├── .gitignore ├── .npmignore ├── .travis.yml ├── .tx └── config ├── Cakefile ├── LICENSE ├── README.md ├── build ├── client │ ├── app │ │ └── locales │ │ │ ├── de.js │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ └── fr.js │ └── public │ │ ├── android-chrome-144x144.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-36x36.png │ │ ├── android-chrome-48x48.png │ │ ├── android-chrome-72x72.png │ │ ├── android-chrome-96x96.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-194x194.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── fonts │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── fonts.css │ │ ├── mavenpro-bold.woff │ │ ├── mavenpro-bold.woff2 │ │ ├── mavenpro-regular.woff │ │ ├── mavenpro-regular.woff2 │ │ ├── sourcesanspro-bold-italic.woff │ │ ├── sourcesanspro-bold-italic.woff2 │ │ ├── sourcesanspro-bold.woff │ │ ├── sourcesanspro-bold.woff2 │ │ ├── sourcesanspro-italic.woff │ │ ├── sourcesanspro-italic.woff2 │ │ ├── sourcesanspro-regular.woff │ │ └── sourcesanspro-regular.woff2 │ │ ├── icons │ │ ├── main_icon.png │ │ ├── main_icon.xcf │ │ └── main_icon_ico.xcf │ │ ├── images │ │ ├── defaultpicture.png │ │ ├── search-icon-mobile.png │ │ ├── search-icon.png │ │ └── spinner.svg │ │ ├── img │ │ ├── cozy-logo.png │ │ ├── create.gif │ │ ├── error.gif │ │ ├── glyphicons-halflings-white.png │ │ ├── glyphicons-halflings.png │ │ ├── loading.gif │ │ ├── new-galery.png │ │ └── nophotos.gif │ │ ├── javascripts │ │ ├── app.js │ │ ├── app.js.map │ │ ├── vendor.js │ │ └── vendor.js.map │ │ ├── leaflet-images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ └── marker-shadow.png │ │ ├── manifest.json │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ └── stylesheets │ │ ├── app.css │ │ ├── app.css.map │ │ ├── vendor.css │ │ └── vendor.css.map ├── server.js └── server │ ├── config.js │ ├── controllers │ ├── album.js │ ├── contact.js │ ├── file.js │ ├── photo.js │ ├── routes.js │ └── sharing.js │ ├── helpers │ ├── downloader.js │ ├── errors.js │ ├── helpers.js │ ├── initializer.js │ ├── localization_manager.js │ ├── photo.js │ ├── sharing.js │ └── thumb.js │ ├── img │ └── error.gif │ ├── locales │ ├── de.js │ ├── en.js │ ├── es.js │ └── fr.js │ ├── models │ ├── album.js │ ├── contact.js │ ├── file.js │ ├── photo.js │ └── requests.js │ └── views │ ├── en_sharemail.js │ ├── fr_sharemail.js │ └── index.js ├── client ├── _specs │ ├── fakeserver.coffee │ ├── index.html │ ├── lib │ │ ├── base_view.coffee │ │ ├── baseview.coffee │ │ └── view_collection.coffee │ ├── models │ │ └── album.coffee │ ├── specs.js │ └── views │ │ └── helpers.coffee ├── app │ ├── application.coffee │ ├── assets │ │ ├── android-chrome-144x144.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-36x36.png │ │ ├── android-chrome-48x48.png │ │ ├── android-chrome-72x72.png │ │ ├── android-chrome-96x96.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-194x194.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── fonts.css │ │ │ ├── mavenpro-bold.woff │ │ │ ├── mavenpro-bold.woff2 │ │ │ ├── mavenpro-regular.woff │ │ │ ├── mavenpro-regular.woff2 │ │ │ ├── sourcesanspro-bold-italic.woff │ │ │ ├── sourcesanspro-bold-italic.woff2 │ │ │ ├── sourcesanspro-bold.woff │ │ │ ├── sourcesanspro-bold.woff2 │ │ │ ├── sourcesanspro-italic.woff │ │ │ ├── sourcesanspro-italic.woff2 │ │ │ ├── sourcesanspro-regular.woff │ │ │ └── sourcesanspro-regular.woff2 │ │ ├── icons │ │ │ ├── main_icon.png │ │ │ ├── main_icon.xcf │ │ │ └── main_icon_ico.xcf │ │ ├── images │ │ │ ├── defaultpicture.png │ │ │ ├── search-icon-mobile.png │ │ │ ├── search-icon.png │ │ │ └── spinner.svg │ │ ├── img │ │ │ ├── cozy-logo.png │ │ │ ├── create.gif │ │ │ ├── error.gif │ │ │ ├── glyphicons-halflings-white.png │ │ │ ├── glyphicons-halflings.png │ │ │ ├── loading.gif │ │ │ ├── new-galery.png │ │ │ └── nophotos.gif │ │ ├── leaflet-images │ │ │ ├── layers-2x.png │ │ │ ├── layers.png │ │ │ ├── marker-icon-2x.png │ │ │ ├── marker-icon.png │ │ │ └── marker-shadow.png │ │ ├── manifest.json │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ └── mstile-70x70.png │ ├── collections │ │ ├── album.coffee │ │ └── photo.coffee │ ├── initialize.coffee │ ├── lib │ │ ├── base_view.coffee │ │ ├── client.coffee │ │ ├── clipboard.coffee │ │ ├── helpers.coffee │ │ ├── map_providers.coffee │ │ ├── modal.coffee │ │ ├── socket_listener.coffee │ │ └── view_collection.coffee │ ├── locales │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ └── fr.json │ ├── models │ │ ├── album.coffee │ │ ├── photo.coffee │ │ ├── photoprocessor.coffee │ │ └── thumbprocessor.coffee │ ├── router.coffee │ ├── styles │ │ ├── _cozy.styl │ │ └── application.styl │ ├── templates │ │ ├── album.jade │ │ ├── albumlist.jade │ │ ├── albumlist_item.jade │ │ ├── browser.jade │ │ ├── galery.jade │ │ ├── map.jade │ │ └── photo.jade │ └── views │ │ ├── album.coffee │ │ ├── albumslist.coffee │ │ ├── albumslist_item.coffee │ │ ├── browser.coffee │ │ ├── galery.coffee │ │ ├── map.coffee │ │ └── photo.coffee ├── config.coffee ├── package.json └── vendor │ ├── scripts │ ├── async.js │ ├── backbone.js │ ├── bootstrap.js │ ├── clearance.js │ ├── cozy-realtime.js │ ├── jade-runtime.js │ ├── jquery-1.9.1.js │ ├── jquery.unveil.js │ ├── leaflet-providers.js │ ├── leaflet-search.js │ ├── leaflet.js │ ├── leaflet.markercluster-src.js │ ├── photobox.js │ ├── polyglot.js │ ├── purl.js │ ├── socket.io.js │ ├── spin.js │ └── underscore.js │ └── styles │ ├── MarkerCluster.Default.css │ ├── MarkerCluster.css │ ├── bootstrap.min.css │ ├── leaflet-search.css │ ├── leaflet-search.mobile.css │ ├── leaflet.css │ └── photobox.css ├── coffeelint.json ├── package.json ├── server.coffee ├── server ├── config.coffee ├── controllers │ ├── album.coffee │ ├── contact.coffee │ ├── file.coffee │ ├── photo.coffee │ ├── routes.coffee │ └── sharing.coffee ├── helpers │ ├── downloader.coffee │ ├── errors.coffee │ ├── helpers.coffee │ ├── initializer.coffee │ ├── localization_manager.coffee │ ├── photo.coffee │ ├── sharing.coffee │ └── thumb.coffee ├── img │ └── error.gif ├── locales │ ├── de.json │ ├── en.json │ ├── es.json │ └── fr.json ├── models │ ├── album.coffee │ ├── contact.coffee │ ├── file.coffee │ ├── photo.coffee │ └── requests.coffee └── views │ ├── en_sharemail.jade │ ├── fr_sharemail.jade │ └── index.jade └── tests ├── fixtures ├── album.zip ├── data.coffee ├── screen.jpg ├── test.jpg └── thumb.jpg ├── helpers.coffee ├── read.coffee └── write.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | 15 | node_modules 16 | uploads 17 | client/public 18 | 19 | npm-debug.log 20 | test-get.zip 21 | *.transifex.coffee 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | lib-cov 3 | *.seed 4 | *.csv 5 | *.log 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | client 12 | !build/client 13 | server 14 | !build/server 15 | tests 16 | /server.coffee 17 | .tx 18 | .travis.yml 19 | Cakefile 20 | coffeelint.json 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | matrix: 4 | fast_finish: true 5 | allow_failures: 6 | - node_js: "5" 7 | node_js: 8 | - 0.10 9 | - 0.12 10 | - 4 11 | - 5 12 | services: 13 | - couchdb 14 | env: 15 | global: 16 | - NODE_ENV=test 17 | - INDEXES_PATH=/home/travis/build/indexes 18 | - CXX=g++-4.8 19 | addons: 20 | apt: 21 | sources: 22 | - ubuntu-toolchain-r-test 23 | packages: 24 | - gcc-4.8 25 | - g++-4.8 26 | 27 | before_install: 28 | - travis_retry git clone git://github.com/cozy/cozy-data-system.git 29 | - cd cozy-data-system 30 | - travis_retry npm install forever coffee-script -g 31 | - travis_retry npm install # data-system 32 | - pwd 33 | - NAME=data-system TOKEN=token forever start -o forever-ds.log -e forever-ds-err.log build/server.js 34 | - ps aux | grep server.js 35 | - sleep 5 36 | - cat forever-ds.log 37 | - curl http://localhost:9101/ 38 | - coffee commands.coffee test-install photos 39 | - cd .. 40 | - export NAME=photos 41 | - export TOKEN=apptoken 42 | - mkdir -p build/client/public/img/ 43 | - cp client/app/assets/img/cozy-logo.png build/client/public/img/ 44 | 45 | script: 46 | - npm test 47 | 48 | after_failure: 49 | - pwd 50 | - ps aux | grep server.js 51 | - netstat -lntp 52 | - cat /home/travis/build/cozy/cozy-photos/cozy-data-system/forever-ds.log 53 | - cat /home/travis/build/cozy/cozy-photos/cozy-data-system/forever-ds-err.log 54 | - curl -v http://localhost:9101/ 55 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [cozy-photos.enjson] 5 | file_filter = client/app/locales/.json 6 | source_file = client/app/locales/en.json 7 | source_lang = en 8 | type = KEYVALUEJSON 9 | 10 | [cozy-photos.server-enjson] 11 | file_filter = server/locales/.json 12 | source_file = server/locales/en.json 13 | source_lang = en 14 | type = KEYVALUEJSON 15 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | {exec} = require 'child_process' 3 | 4 | option '-f' , '--file [FILE*]' , 'test file to run' 5 | option '' , '--dir [DIR*]' , 'directory where to grab test files' 6 | option '-d' , '--debug' , 'run node in debug mode' 7 | option '-b' , '--debug-brk' , 'run node in --debug-brk mode (stops on first line)' 8 | 9 | options = # defaults, will be overwritten by command line options 10 | file : no 11 | dir : no 12 | debug : no 13 | 'debug-brk' : no 14 | 15 | logger = require('printit') 16 | date: false 17 | prefix: 'cake' 18 | 19 | 20 | # Grab test files of a directory 21 | walk = (dir, fileList) -> 22 | list = fs.readdirSync(dir) 23 | if list 24 | for file in list 25 | if file 26 | filename = dir + '/' + file 27 | stat = fs.statSync(filename) 28 | if stat and stat.isDirectory() 29 | walk(filename, fileList) 30 | else if filename.substr(-6) == "coffee" 31 | fileList.push(filename) 32 | return fileList 33 | 34 | task 'tests', 'run server tests, ./test is parsed by default, otherwise use -f or --dir', (opts) -> 35 | options = opts 36 | testFiles = [] 37 | if options.dir 38 | dirList = options.dir 39 | testFiles = walk(dir, testFiles) for dir in dirList 40 | if options.file 41 | testFiles = testFiles.concat(options.file) 42 | if not(options.dir or options.file) 43 | testFiles = walk("tests", []) 44 | runTests testFiles 45 | 46 | task 'tests:client', 'run client tests through mocha', (opts) -> 47 | exec "mocha-phantomjs client/_specs/index.html", (err, stdout, stderr) -> 48 | if err 49 | console.log "Running mocha caught exception: \n" + err 50 | console.log stderr 51 | console.log stdout 52 | 53 | 54 | runTests = (fileList) -> 55 | command = "mocha " + fileList.join(" ") + " " 56 | if options['debug-brk'] 57 | command += "--debug-brk --forward-io --profile " 58 | if options.debug 59 | command += "--debug --forward-io --profile " 60 | command += " --reporter spec --compilers coffee:coffee-script/register --colors" 61 | exec command, (err, stdout, stderr) -> 62 | if err 63 | console.log "Running mocha caught exception: \n" + err 64 | console.log stdout 65 | 66 | process.exit if err then 1 else 0 67 | 68 | buildJade = -> 69 | jade = require 'jade' 70 | path = require 'path' 71 | for file in fs.readdirSync './server/views/' 72 | return unless path.extname(file) is '.jade' 73 | filename = "./server/views/#{file}" 74 | template = fs.readFileSync filename, 'utf8' 75 | output = "var jade = require('jade/runtime');\n" 76 | output += "module.exports = " + jade.compileClient template, {filename} 77 | name = file.replace '.jade', '.js' 78 | fs.writeFileSync "./build/server/views/#{name}", output 79 | 80 | # convert JSON lang files to JS 81 | buildJsInLocales = -> 82 | path = require 'path' 83 | for file in fs.readdirSync './client/app/locales/' 84 | filename = './client/app/locales/' + file 85 | template = fs.readFileSync filename, 'utf8' 86 | exported = "module.exports = #{template};\n" 87 | name = file.replace '.json', '.js' 88 | fs.writeFileSync "./build/client/app/locales/#{name}", exported 89 | # server files 90 | for file in fs.readdirSync './server/locales/' 91 | filename = './server/locales/' + file 92 | template = fs.readFileSync filename, 'utf8' 93 | exported = "module.exports = #{template};\n" 94 | name = file.replace '.json', '.js' 95 | fs.writeFileSync "./build/server/locales/#{name}", exported 96 | 97 | task 'build', 'Build CoffeeScript to Javascript', -> 98 | logger.options.prefix = 'cake:build' 99 | logger.info "Start compilation..." 100 | command = "coffee -cb --output build/server server && " + \ 101 | "coffee -cb --output build/ server.coffee && " + \ 102 | "cp -R server/img build/server && " + \ 103 | "rm -rf build/client && mkdir build/client && " + \ 104 | "mkdir -p build/client/app/locales/ && " + \ 105 | "rm -rf build/client/app/locales/* && " + \ 106 | "cd client/ && brunch build --production && cd .. &&" + \ 107 | "cp -R client/public build/client/" 108 | 109 | exec command, (err, stdout, stderr) -> 110 | if err 111 | logger.error "An error has occurred while compiling:\n" + err 112 | process.exit 1 113 | else 114 | buildJade() 115 | buildJsInLocales() 116 | logger.info "Compilation succeeded." 117 | process.exit 0 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Cozy](https://cozy.io) Photos 2 | 3 | Cozy Photos makes your photo management easy. Main features are: 4 | 5 | * Simple UI 6 | * Photo upload 7 | * Galleries 8 | * Gallery Sharing 9 | 10 | ## Install 11 | 12 | We assume here that the Cozy platform is correctly [installed](https://docs.cozy.io/en/host/install) 13 | on your server. 14 | 15 | You can simply install the Photos application via the app registry. Click on ythe *Chose Your Apps* button located on the right of your Cozy Home. 16 | 17 | From the command line you can type this command: 18 | 19 | cozy-monitor install photos 20 | 21 | 22 | ## Contribution 23 | 24 | You can contribute to the Cozy Photos in many ways: 25 | 26 | * Pick up an [issue](https://github.com/cozy/cozy-photos/issues?state=open) and solve it. 27 | * Translate it in [a new language](https://github.com/cozy/cozy-photos/tree/master/client/app/locales). 28 | * Photo tagging 29 | * Contact tagging 30 | * Last added photo stream 31 | 32 | 33 | ## Hack 34 | 35 | Hacking the Photos app requires you [setup a dev environment](https://docs.cozy.io/en/hack/getting-started/). Once it's done you can hack Cozy Contact just like it was your own app. 36 | 37 | git clone https://github.com/cozy/cozy-photos.git 38 | 39 | Run it with: 40 | 41 | node server.js 42 | 43 | Each modification of the server requires a new build, here is how to run a 44 | build: 45 | 46 | cake build 47 | 48 | Each modification of the client requires a specific build too. 49 | 50 | cd client 51 | brunch build 52 | 53 | Make sure you have installed imagemagick on your local system otherwise you won't be able to much. 54 | 55 | ## Tests 56 | 57 | ![Build Status](https://travis-ci.org/cozy/cozy-photos.png?branch=master) 58 | 59 | To run tests type the following command into the Cozy Photos folder: 60 | 61 | cake tests 62 | 63 | In order to run the tests, you must only have the Data System started. 64 | 65 | ## Icons 66 | 67 | by [iconmonstr](http://iconmonstr.com/) 68 | 69 | Main icon by [Elegant Themes](http://www.elegantthemes.com/blog/freebie-of-the-week/beautiful-flat-icons-for-free). 70 | 71 | ## Contribute with Transifex 72 | 73 | Transifex can be used the same way as git. It can push or pull translations. The config file in the .tx repository configure the way Transifex is working : it will get the json files from the client/app/locales repository. 74 | If you want to learn more about how to use this tool, I'll invite you to check [this](http://docs.transifex.com/introduction/) tutorial. 75 | 76 | ## License 77 | 78 | Cozy Photos is developed by Cozy Cloud and distributed under the AGPL v3 license. 79 | 80 | ## What is Cozy? 81 | 82 | ![Cozy Logo](https://raw.github.com/cozy/cozy-setup/gh-pages/assets/images/happycloud.png) 83 | 84 | [Cozy](https://cozy.io) is a platform that brings all your web services in the 85 | same private space. With it, your web apps and your devices can share data 86 | easily, providing you 87 | with a new experience. You can install Cozy on your own hardware where no one 88 | profiles you. 89 | 90 | ## Community 91 | 92 | You can reach the Cozy Community by: 93 | 94 | * Chatting with us on IRC #cozycloud on irc.freenode.net 95 | * Posting on our [Forum](https://forum.cozy.io/) 96 | * Posting issues on the [Github repos](https://github.com/cozy/) 97 | * Mentioning us on [Twitter](https://twitter.com/mycozycloud) 98 | -------------------------------------------------------------------------------- /build/client/app/locales/en.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "or": "or", 3 | "Back": "Back", 4 | "Create a new album": "Create a new album", 5 | "Delete": "Delete", 6 | "Download": "Download", 7 | "Edit": "Edit", 8 | "Stop editing": "Save Changes", 9 | "It will appear on your homepage.": "It will be displayed on the album page.", 10 | "Make it Hidden": "hidden", 11 | "Make it Private": "private", 12 | "Make it Public": "public", 13 | "New": "New", 14 | "private": "private", 15 | "public": "public", 16 | "hidden": "hidden", 17 | "There is no photos in this album": "There is no photo in this album. Click on Edit button to add new ones.", 18 | "There is no public albums.": "There are no albums.", 19 | "This album is private": "This album is private", 20 | "This album is hidden": "This album is hidden", 21 | "This album is public": "This album is public", 22 | "title placeholder": "Set a title for this album…", 23 | "View": "View", 24 | "description placeholder": "Write a description…", 25 | "is too big (max 10Mo)": "is too big (max 10Mo)", 26 | "is not an image": "is not an image", 27 | "Share album by mail": "Share album by mail", 28 | "Upload your contacts...": "Upload your contacts…", 29 | "Share album": "Share album", 30 | "Add contact": "Add contact", 31 | "Send mail": "Send mail", 32 | "Select your friends": "Select your friends", 33 | "Add": "Add", 34 | "Cancel": "Cancel", 35 | "photo successfully set as cover": "The picture has been successfully set as album cover", 36 | "problem occured while setting cover": "A problem occured while setting picture as cover", 37 | "pick from computer": "Click here or drag your photos below to add them to the album.", 38 | "pick from files": "Click here to pick pictures from the Files app.", 39 | "hidden-description": "It will not appear on your homepage.\nBut you can share it with the following url:", 40 | "It cannot be accessed from the public side": "It is not a public resource.", 41 | "rebuild thumbnails": "Rebuild thumbnails", 42 | "01": "January", 43 | "02": "February", 44 | "03": "March", 45 | "04": "April", 46 | "05": "May", 47 | "06": "June", 48 | "07": "July", 49 | "08": "August", 50 | "09": "September", 51 | "10": "October", 52 | "11": "November", 53 | "12": "December", 54 | "cancel": "Cancel", 55 | "copy paste link": "To give access to your contact send him/her the link below:", 56 | "details": "Details", 57 | "inherited from": "inherited from", 58 | "modal question album shareable": "Select share mode for this album", 59 | "modal shared album custom msg": "Enter email and press enter", 60 | "modal shared album link msg": "Send this link to let people access this album", 61 | "modal shared public link msg": "Send this link to let people access this folder:", 62 | "modal shared with people msg": "Invite a selection of contacts to access it. Type\nemail in the field and press enter (An email will be sent to them):", 63 | "modal send mails": "Send a notification", 64 | "modal next": "Next", 65 | "modal prev": "Previous", 66 | "modal ok": "Ok", 67 | "modal cancel": "Cancel", 68 | "modal error": "Error", 69 | "only you can see": "Only you and the people listed below can access this resource", 70 | "public": "Public", 71 | "private": "Private", 72 | "shared": "Shared", 73 | "share": "Share", 74 | "save": "Save", 75 | "see link": "See link", 76 | "send mails question": "Send a notification email to:", 77 | "sharing": "Sharing", 78 | "revoke": "Revoke", 79 | "confirm": "Confirm", 80 | "share forgot add": "Looks like you forgot to click the Add button", 81 | "share confirm save": "The changes you made to the permissions will not be saved. Do you want to continue?", 82 | "yes forgot": "Back", 83 | "no forgot": "It's ok", 84 | "perm": "can ", 85 | "perm r album": "browse this album", 86 | "perm rw album": "browse and upload photos", 87 | "mail not send": "Mail not sent", 88 | "server error occured": "Error occured on server side, please try again later", 89 | "change notif": "Check this box to be notified when a contact\nwill add a photo to this album.", 90 | "send email hint": "Notification emails will be sent one time on save", 91 | "yes": "Yes", 92 | "no": "No", 93 | "picture": "picture |||| pictures", 94 | "delete empty album": "This album is empty, do you want to delete it?", 95 | "are you sure you want to delete this album": "Are you sure you want to delete this album?", 96 | "photos search": "Loading ...", 97 | "no photos found": "No photos found", 98 | "thumb creation": "Application creates thumbs for files.", 99 | "progress": "Progression", 100 | "Navigate before upload": "Some upload are in progress, do you really want to leave this page?", 101 | "application title": "Cozy - photos", 102 | "r": "read only", 103 | "photo delete confirm": "Are you sure you want to delete this photo?" 104 | } 105 | ; 106 | -------------------------------------------------------------------------------- /build/client/public/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/android-chrome-144x144.png -------------------------------------------------------------------------------- /build/client/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /build/client/public/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/android-chrome-36x36.png -------------------------------------------------------------------------------- /build/client/public/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/android-chrome-48x48.png -------------------------------------------------------------------------------- /build/client/public/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/android-chrome-72x72.png -------------------------------------------------------------------------------- /build/client/public/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/android-chrome-96x96.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /build/client/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/apple-touch-icon.png -------------------------------------------------------------------------------- /build/client/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #cdb19b 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build/client/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/favicon-16x16.png -------------------------------------------------------------------------------- /build/client/public/favicon-194x194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/favicon-194x194.png -------------------------------------------------------------------------------- /build/client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /build/client/public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/favicon-96x96.png -------------------------------------------------------------------------------- /build/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/favicon.ico -------------------------------------------------------------------------------- /build/client/public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /build/client/public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /build/client/public/fonts/mavenpro-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/mavenpro-bold.woff -------------------------------------------------------------------------------- /build/client/public/fonts/mavenpro-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/mavenpro-bold.woff2 -------------------------------------------------------------------------------- /build/client/public/fonts/mavenpro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/mavenpro-regular.woff -------------------------------------------------------------------------------- /build/client/public/fonts/mavenpro-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/mavenpro-regular.woff2 -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-bold-italic.woff -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-bold-italic.woff2 -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-bold.woff -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-bold.woff2 -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-italic.woff -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-italic.woff2 -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-regular.woff -------------------------------------------------------------------------------- /build/client/public/fonts/sourcesanspro-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/fonts/sourcesanspro-regular.woff2 -------------------------------------------------------------------------------- /build/client/public/icons/main_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/icons/main_icon.png -------------------------------------------------------------------------------- /build/client/public/icons/main_icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/icons/main_icon.xcf -------------------------------------------------------------------------------- /build/client/public/icons/main_icon_ico.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/icons/main_icon_ico.xcf -------------------------------------------------------------------------------- /build/client/public/images/defaultpicture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/images/defaultpicture.png -------------------------------------------------------------------------------- /build/client/public/images/search-icon-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/images/search-icon-mobile.png -------------------------------------------------------------------------------- /build/client/public/images/search-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/images/search-icon.png -------------------------------------------------------------------------------- /build/client/public/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/client/public/img/cozy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/cozy-logo.png -------------------------------------------------------------------------------- /build/client/public/img/create.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/create.gif -------------------------------------------------------------------------------- /build/client/public/img/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/error.gif -------------------------------------------------------------------------------- /build/client/public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /build/client/public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /build/client/public/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/loading.gif -------------------------------------------------------------------------------- /build/client/public/img/new-galery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/new-galery.png -------------------------------------------------------------------------------- /build/client/public/img/nophotos.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/img/nophotos.gif -------------------------------------------------------------------------------- /build/client/public/leaflet-images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/leaflet-images/layers-2x.png -------------------------------------------------------------------------------- /build/client/public/leaflet-images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/leaflet-images/layers.png -------------------------------------------------------------------------------- /build/client/public/leaflet-images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/leaflet-images/marker-icon-2x.png -------------------------------------------------------------------------------- /build/client/public/leaflet-images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/leaflet-images/marker-icon.png -------------------------------------------------------------------------------- /build/client/public/leaflet-images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/leaflet-images/marker-shadow.png -------------------------------------------------------------------------------- /build/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cozy Photos", 3 | "icons": [ 4 | { 5 | "src": "\/apps\/photos\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/apps\/photos\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/apps\/photos\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/apps\/photos\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/apps\/photos\/android-chrome-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/apps\/photos\/android-chrome-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /build/client/public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/mstile-144x144.png -------------------------------------------------------------------------------- /build/client/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/mstile-150x150.png -------------------------------------------------------------------------------- /build/client/public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/mstile-310x150.png -------------------------------------------------------------------------------- /build/client/public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/mstile-310x310.png -------------------------------------------------------------------------------- /build/client/public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/client/public/mstile-70x70.png -------------------------------------------------------------------------------- /build/server.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Photo, americano, start; 3 | 4 | americano = require('americano'); 5 | 6 | Photo = require('./server/models/photo'); 7 | 8 | process.on('uncaughtException', function(err) { 9 | console.log(err); 10 | console.log(err.stack); 11 | return setTimeout(function() { 12 | return process.exit(1); 13 | }, 1000); 14 | }); 15 | 16 | module.exports.start = start = function(options, cb) { 17 | options.name = 'cozy-photos'; 18 | if (options.root == null) { 19 | options.root = __dirname; 20 | } 21 | if (options.port == null) { 22 | options.port = 9119; 23 | } 24 | if (options.host == null) { 25 | options.host = '127.0.0.1'; 26 | } 27 | return americano.start(options, function(err, app, server) { 28 | if (err) { 29 | return cb(err); 30 | } 31 | Photo.patchGps(function(err) { 32 | if (err != null) { 33 | return console.log("Something went wrong during patch -- " + err); 34 | } else { 35 | return console.log("Patch successfully applied"); 36 | } 37 | }); 38 | module.exports.app = app; 39 | return typeof cb === "function" ? cb(null, app, server) : void 0; 40 | }); 41 | }; 42 | 43 | if (!module.parent) { 44 | start({ 45 | port: process.env.PORT, 46 | host: process.env.HOST 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /build/server/config.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var File, RealtimeAdapter, americano, fs, init, localizationManager, os, path, publicStatic, sharing, staticMiddleware, thumb, useBuildView; 3 | 4 | americano = require('americano'); 5 | 6 | sharing = require('./controllers/sharing'); 7 | 8 | path = require('path'); 9 | 10 | fs = require('fs'); 11 | 12 | localizationManager = require('./helpers/localization_manager'); 13 | 14 | RealtimeAdapter = require('cozy-realtime-adapter'); 15 | 16 | init = require('./helpers/initializer'); 17 | 18 | thumb = require('./helpers/thumb').create; 19 | 20 | File = require('./models/file'); 21 | 22 | path = require('path'); 23 | 24 | os = require('os'); 25 | 26 | staticMiddleware = americano["static"](__dirname + '/../client/public', { 27 | maxAge: 86400000 28 | }); 29 | 30 | publicStatic = function(req, res, next) { 31 | var url; 32 | url = req.url; 33 | req.url = req.url.replace('/public', ''); 34 | return staticMiddleware(req, res, function(err) { 35 | req.url = url; 36 | return next(err); 37 | }); 38 | }; 39 | 40 | useBuildView = fs.existsSync(path.resolve(__dirname, 'views/index.js')); 41 | 42 | module.exports = { 43 | common: { 44 | set: { 45 | 'view engine': useBuildView ? 'js' : 'jade', 46 | 'views': path.resolve(__dirname, 'views') 47 | }, 48 | engine: { 49 | js: function(path, locales, callback) { 50 | return callback(null, require(path)(locales)); 51 | } 52 | }, 53 | use: [americano.methodOverride(), americano.bodyParser(), staticMiddleware, publicStatic, sharing.markPublicRequests], 54 | useAfter: [ 55 | americano.errorHandler({ 56 | dumpExceptions: true, 57 | showStack: true 58 | }) 59 | ], 60 | afterStart: function(app, server) { 61 | var err, error, error1, patterns, realtime, uploadFolder, viewEngine; 62 | app.server = server; 63 | require('./controllers/photo').setApp(app); 64 | viewEngine = app.render.bind(app); 65 | localizationManager.setRenderer(viewEngine); 66 | uploadFolder = path.join(os.tmpdir(), 'uploads'); 67 | try { 68 | fs.mkdirSync(uploadFolder); 69 | } catch (error) { 70 | err = error; 71 | if (err.code !== 'EEXIST') { 72 | console.log("Something went wrong while creating uploads folder"); 73 | console.log(err); 74 | } 75 | } 76 | try { 77 | fs.chmodSync(uploadFolder, '0700'); 78 | } catch (error1) { 79 | err = error1; 80 | console.log("Something went wrong while setting rights on upload folder"); 81 | console.log(err); 82 | } 83 | patterns = ['contact.*', 'album.*', 'photo.*']; 84 | return realtime = RealtimeAdapter(server, patterns); 85 | } 86 | }, 87 | development: [americano.logger('dev')], 88 | production: [americano.logger('short')], 89 | plugins: ['cozydb'] 90 | }; 91 | -------------------------------------------------------------------------------- /build/server/controllers/contact.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Contact, async, fs; 3 | 4 | Contact = require('../models/contact'); 5 | 6 | async = require('async'); 7 | 8 | fs = require('fs'); 9 | 10 | module.exports.list = function(req, res, next) { 11 | return Contact.all(function(err, contacts) { 12 | if (err) { 13 | return next(err); 14 | } else if (!contacts) { 15 | err = new Error("Contacts not found"); 16 | err.status = 404; 17 | return next(err); 18 | } else { 19 | return res.send(contacts); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /build/server/controllers/routes.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var album, contact, file, photo, sharing; 3 | 4 | photo = require('./photo'); 5 | 6 | album = require('./album'); 7 | 8 | file = require('./file'); 9 | 10 | contact = require('./contact'); 11 | 12 | sharing = require('./sharing'); 13 | 14 | module.exports = { 15 | 'albumid': { 16 | param: album.fetch 17 | }, 18 | 'photoid': { 19 | param: photo.fetch 20 | }, 21 | 'fileid': { 22 | param: file.fetch 23 | }, 24 | '': { 25 | get: album.index 26 | }, 27 | 'albums/?': { 28 | get: album.list, 29 | post: album.create 30 | }, 31 | 'albums/:albumid.zip': { 32 | get: album.zip 33 | }, 34 | 'albums/:albumid/?': { 35 | get: album.read, 36 | put: album.update, 37 | "delete": album["delete"] 38 | }, 39 | 'files/': { 40 | get: file.list 41 | }, 42 | 'files/:page': { 43 | get: file.list 44 | }, 45 | 'files/thumbs/:fileid': { 46 | get: file.thumb 47 | }, 48 | 'files/:fileid/toPhoto': { 49 | post: file.createPhoto 50 | }, 51 | 'photos/?': { 52 | post: photo.create 53 | }, 54 | 'photos/:photoid/?': { 55 | put: photo.update, 56 | "delete": photo["delete"] 57 | }, 58 | 'photos/:photoid.jpg': { 59 | get: photo.screen 60 | }, 61 | 'photos/thumbs/:photoid.jpg': { 62 | get: photo.thumb, 63 | put: photo.updateThumb 64 | }, 65 | 'photos/raws/:photoid.jpg': { 66 | get: photo.raw 67 | }, 68 | 'photos/': { 69 | get: photo.fetchAll 70 | }, 71 | 'public/?': { 72 | get: album.index 73 | }, 74 | 'public/albums/?': { 75 | get: album.list 76 | }, 77 | 'public/albums/:albumid.zip': { 78 | get: album.zip 79 | }, 80 | 'public/albums/:albumid/?': { 81 | get: album.read 82 | }, 83 | 'public/photos/:photoid.jpg': { 84 | get: photo.screen 85 | }, 86 | 'public/photos/thumbs/:photoid.jpg': { 87 | get: photo.thumb 88 | }, 89 | 'public/photos/raws/:photoid.jpg': { 90 | get: photo.raw 91 | }, 92 | 'shareid': { 93 | param: sharing.fetch 94 | }, 95 | 'clearance/contacts': { 96 | get: sharing.contactList 97 | }, 98 | 'clearance/contacts/:contactid.jpg': { 99 | get: sharing.contactPicture 100 | }, 101 | 'clearance/contacts/:contactid': { 102 | get: sharing.contact 103 | }, 104 | 'clearance/:shareid': { 105 | put: sharing.change 106 | }, 107 | 'clearance/:shareid/send': { 108 | post: sharing.sendAll 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /build/server/controllers/sharing.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Album, async, cache, clearance, clearanceCtl, cozydb, fs, getUser, localizationManager; 3 | 4 | async = require('async'); 5 | 6 | clearance = require('cozy-clearance'); 7 | 8 | cozydb = require('cozydb'); 9 | 10 | fs = require('fs'); 11 | 12 | Album = require('../models/album'); 13 | 14 | localizationManager = require('../helpers/localization_manager'); 15 | 16 | getUser = function(callback) { 17 | return cozydb.api.getCozyUser(function(err, user) { 18 | if (err) { 19 | return callback(err); 20 | } 21 | if ((user != null ? user.public_name : void 0) && user.public_name.length > 0) { 22 | return callback(null, { 23 | name: user.public_name, 24 | email: user.email 25 | }); 26 | } else { 27 | return localizationManager.ensureReady(function(err) { 28 | return callback(null, { 29 | name: localizationManager.t('default user name'), 30 | email: null 31 | }); 32 | }); 33 | } 34 | }); 35 | }; 36 | 37 | clearanceCtl = clearance.controller({ 38 | mailTemplate: function(options, callback) { 39 | return getUser(function(err, user) { 40 | if (err != null) { 41 | return callback(err); 42 | } else { 43 | options.displayName = user.name; 44 | options.displayEmail = user.email; 45 | return localizationManager.render('sharemail', options, callback); 46 | } 47 | }); 48 | }, 49 | mailSubject: function(options, callback) { 50 | return getUser(function(err, user) { 51 | if (err != null) { 52 | return callback(err); 53 | } else { 54 | return localizationManager.ensureReady(function(err) { 55 | return callback(null, localizationManager.t('email sharing subject', { 56 | displayName: user.name, 57 | name: options.doc.title 58 | })); 59 | }); 60 | } 61 | }); 62 | }, 63 | attachments: [ 64 | { 65 | path: fs.realpathSync('./build/client/public/img/cozy-logo.png'), 66 | filename: 'cozy-logo.png', 67 | cid: 'cozy-logo' 68 | } 69 | ] 70 | }); 71 | 72 | module.exports.fetch = function(req, res, next, id) { 73 | return Album.find(id, function(err, album) { 74 | if (album) { 75 | req.doc = album; 76 | return next(); 77 | } else { 78 | err = new Error('bad usage'); 79 | err.status = 400; 80 | return next(err); 81 | } 82 | }); 83 | }; 84 | 85 | module.exports.markPublicRequests = function(req, res, next) { 86 | if (req.url.match(/^\/public/)) { 87 | req["public"] = true; 88 | } 89 | return next(); 90 | }; 91 | 92 | module.exports.checkPermissions = function(album, req, callback) { 93 | if (!req["public"]) { 94 | return callback(null, true); 95 | } 96 | if (album.clearance === 'hidden') { 97 | album.clearance = 'public'; 98 | } 99 | if (album.clearance === 'private') { 100 | album.clearance = []; 101 | } 102 | return clearance.check(album, 'r', req, callback); 103 | }; 104 | 105 | cache = {}; 106 | 107 | module.exports.checkPermissionsPhoto = function(photo, perm, req, callback) { 108 | var albumid, incache; 109 | if (!req["public"]) { 110 | return callback(null, true); 111 | } 112 | albumid = photo.albumid; 113 | incache = cache[albumid]; 114 | if (incache) { 115 | return clearance.check({ 116 | clearance: incache 117 | }, perm, req, callback); 118 | } else { 119 | return Album.find(albumid, function(err, album) { 120 | if (err || !album) { 121 | return callback(null, false); 122 | } 123 | if (album.clearance === 'hidden') { 124 | album.clearance = 'public'; 125 | } 126 | if (album.clearance === 'private') { 127 | album.clearance = []; 128 | } 129 | cache[albumid] = album.clearance; 130 | return clearance.check(album, perm, req, callback); 131 | }); 132 | } 133 | }; 134 | 135 | module.exports.change = function(req, res, next) { 136 | cache[req.params.shareid] = null; 137 | return clearanceCtl.change(req, res, next); 138 | }; 139 | 140 | module.exports.sendAll = clearanceCtl.sendAll; 141 | 142 | module.exports.contactList = clearanceCtl.contactList; 143 | 144 | module.exports.contact = clearanceCtl.contact; 145 | 146 | module.exports.contactPicture = clearanceCtl.contactPicture; 147 | -------------------------------------------------------------------------------- /build/server/helpers/downloader.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var http; 3 | 4 | http = require('http'); 5 | 6 | module.exports = { 7 | download: function(path, callback) { 8 | var basic, id, options, pwd; 9 | id = process.env.NAME; 10 | pwd = process.env.TOKEN; 11 | basic = "Basic " + (new Buffer(id + ":" + pwd).toString('base64')); 12 | options = { 13 | host: 'localhost', 14 | port: 9101, 15 | path: path, 16 | headers: { 17 | Authorization: basic 18 | } 19 | }; 20 | return http.get(options, callback); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /build/server/helpers/errors.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | module.exports.NotFound = function(what) { 3 | var err; 4 | err = new Error(what + ': Not Found'); 5 | err.status = 404; 6 | return err; 7 | }; 8 | 9 | module.exports.NotAllowed = function() { 10 | var err; 11 | err = new Error('Not allowed'); 12 | err.status = 401; 13 | return err; 14 | }; 15 | 16 | module.exports.BadUsage = function() { 17 | var err; 18 | err = new Error('Bad Usage'); 19 | err.status = 400; 20 | return err; 21 | }; 22 | -------------------------------------------------------------------------------- /build/server/helpers/helpers.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | module.exports = { 3 | slugify: function(text) { 4 | return text.replace(/[^-a-zA-Z0-9,&\s]+/ig, '').replace(/-/gi, '_').replace(/\s/gi, '-'); 5 | }, 6 | noop: function() {} 7 | }; 8 | -------------------------------------------------------------------------------- /build/server/helpers/initializer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var File, Photo, async, convertImage, thumb; 3 | 4 | Photo = require('../models/photo'); 5 | 6 | File = require('../models/file'); 7 | 8 | thumb = require('./thumb').create; 9 | 10 | async = require('async'); 11 | 12 | convertImage = function(cb) { 13 | var convert; 14 | convert = function(doc, callback) { 15 | var error, error1; 16 | if (doc._attachments != null) { 17 | try { 18 | console.log("Convert " + doc.title + " ..."); 19 | return doc.convertBinary(function(err, res, body) { 20 | if (err != null) { 21 | console.log(err); 22 | } 23 | return callback(err); 24 | }); 25 | } catch (error1) { 26 | error = error1; 27 | console.log("Cannot convert " + doc.title); 28 | return callback(); 29 | } 30 | } else { 31 | return callback(); 32 | } 33 | }; 34 | return Photo.all(function(err, docs) { 35 | if (err) { 36 | return cb(err); 37 | } else { 38 | return async.eachSeries(docs, convert, cb); 39 | } 40 | }); 41 | }; 42 | 43 | module.exports.convert = function(socket, done) { 44 | if (done == null) { 45 | done = function() { 46 | return null; 47 | }; 48 | } 49 | return done(); 50 | }; 51 | -------------------------------------------------------------------------------- /build/server/helpers/localization_manager.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var LocalizationManager, Polyglot, cozydb; 3 | 4 | Polyglot = require('node-polyglot'); 5 | 6 | cozydb = require('cozydb'); 7 | 8 | LocalizationManager = (function() { 9 | function LocalizationManager() {} 10 | 11 | LocalizationManager.prototype.polyglot = null; 12 | 13 | LocalizationManager.prototype.initialize = function(callback) { 14 | if (callback == null) { 15 | callback = function() {}; 16 | } 17 | return this.ensureReady(callback); 18 | }; 19 | 20 | LocalizationManager.prototype.setRenderer = function(renderer) { 21 | return this.renderer = renderer; 22 | }; 23 | 24 | LocalizationManager.prototype.retrieveLocale = function(callback) { 25 | return cozydb.api.getCozyLocale(function(err, locale) { 26 | if ((err != null) || !locale) { 27 | locale = 'en'; 28 | } 29 | return callback(null, locale); 30 | }); 31 | }; 32 | 33 | LocalizationManager.prototype.ensureReady = function(callback) { 34 | if (this.polyglot) { 35 | return callback(null, this.polyglot); 36 | } 37 | return this.retrieveLocale((function(_this) { 38 | return function(err, locale) { 39 | var phrases; 40 | if (err) { 41 | return callback(err); 42 | } 43 | phrases = (function() { 44 | var error; 45 | try { 46 | return require("../locales/" + locale); 47 | } catch (error) { 48 | err = error; 49 | return require('../locales/en'); 50 | } 51 | })(); 52 | _this.polyglot = new Polyglot({ 53 | locale: locale, 54 | phrases: phrases 55 | }); 56 | return callback(null, _this.polyglot); 57 | }; 58 | })(this)); 59 | }; 60 | 61 | LocalizationManager.prototype.t = function(key, params) { 62 | var ref; 63 | if (params == null) { 64 | params = {}; 65 | } 66 | return (ref = this.polyglot) != null ? ref.t(key, params) : void 0; 67 | }; 68 | 69 | LocalizationManager.prototype.render = function(name, options, callback) { 70 | return this.ensureReady((function(_this) { 71 | return function(err) { 72 | var viewName; 73 | if (err) { 74 | return callback(err); 75 | } 76 | viewName = _this.polyglot.currentLocale + "_" + name; 77 | return _this.renderer(viewName, options, callback); 78 | }; 79 | })(this)); 80 | }; 81 | 82 | return LocalizationManager; 83 | 84 | })(); 85 | 86 | module.exports = new LocalizationManager(); 87 | -------------------------------------------------------------------------------- /build/server/helpers/photo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var codes, photoHelpers; 3 | 4 | codes = { 5 | TopLeft: 1, 6 | TopRight: 2, 7 | BottomRight: 3, 8 | BottomLeft: 4, 9 | LeftTop: 5, 10 | RightTop: 6, 11 | RightBottom: 7, 12 | LeftBottom: 8 13 | }; 14 | 15 | module.exports = photoHelpers = { 16 | getOrientation: function(orientation) { 17 | var result; 18 | result = 1; 19 | if (typeof orientation === 'number' && orientation > 0 && orientation < 9) { 20 | result = orientation; 21 | } else if (codes[orientation] != null) { 22 | result = codes[orientation]; 23 | } 24 | return result; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /build/server/helpers/sharing.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Album, clearance; 3 | 4 | Album = require('../models/album'); 5 | 6 | clearance = require('cozy-clearance'); 7 | 8 | module.exports.checkClearance = function(doc, req, perm, callback) { 9 | if (typeof perm === "function") { 10 | callback = perm; 11 | perm = 'r'; 12 | } 13 | return clearance.check(doc, perm, req, function(err, result) { 14 | if (result) { 15 | return callback(true); 16 | } else { 17 | return callback(false); 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /build/server/helpers/thumb.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var fs, gm, gpsDegToDec, log, mime, thumb, whiteList; 3 | 4 | fs = require('fs'); 5 | 6 | gm = require('gm'); 7 | 8 | mime = require('mime'); 9 | 10 | log = require('printit')({ 11 | prefix: 'thumbnails' 12 | }); 13 | 14 | whiteList = ['image/jpeg', 'image/png']; 15 | 16 | gpsDegToDec = function(pos, posRef) { 17 | var coord, ref, split, splitAlt; 18 | split = pos.match(/(\d+)\/(\d+), (\d+)\/(\d+), (\d+)\/(\d+)/); 19 | if (split != null ? split[6] : void 0) { 20 | coord = split[1] / split[2] + (split[3] / split[4]) / 60 + (split[5] / split[6]) / 3600; 21 | } else { 22 | splitAlt = pos.match(/(\d+)\/(\d+)/); 23 | if (splitAlt != null) { 24 | coord = splitAlt[1] / splitAlt[2]; 25 | } 26 | } 27 | ref = posRef === 'S' || posRef === 'W' ? -1 : 1; 28 | if (coord != null) { 29 | return ref * coord; 30 | } 31 | }; 32 | 33 | module.exports = thumb = { 34 | readMetadata: function(filePath, callback) { 35 | return gm(filePath).options({ 36 | imageMagick: true 37 | }).identify(function(err, data) { 38 | var GPS, alt, lat, long, metadata, orientation, ref1, ref2, ref3; 39 | if (err) { 40 | return callback(err); 41 | } else { 42 | orientation = data.Orientation; 43 | alt = 'exif:GPSAltitude'; 44 | lat = 'exif:GPSLatitude'; 45 | long = 'exif:GPSLongitude'; 46 | GPS = {}; 47 | if (data.Properties[alt]) { 48 | GPS.alt = gpsDegToDec(data.Properties[alt], data.Properties[alt + 'Ref']); 49 | } 50 | if (data.Properties[lat]) { 51 | GPS.lat = gpsDegToDec(data.Properties[lat], data.Properties[lat + 'Ref']); 52 | } 53 | if (data.Properties[long]) { 54 | GPS.long = gpsDegToDec(data.Properties[long], data.Properties[long + 'Ref']); 55 | } 56 | if (!(orientation != null) || data.Orientation === 'Undefined') { 57 | orientation = 1; 58 | } 59 | metadata = { 60 | exif: { 61 | orientation: orientation, 62 | date: (ref1 = (ref2 = (ref3 = data.Properties['exif:DateTimeOriginal']) != null ? ref3 : data.Properties['exif:DateTimeDigitized']) != null ? ref2 : data.Properties['exif:DateTime']) != null ? ref1 : data.Properties['date:create'], 63 | gps: GPS 64 | } 65 | }; 66 | return callback(null, metadata); 67 | } 68 | }); 69 | }, 70 | attachFile: function(file, dstPath, name, callback) { 71 | return file.attachBinary(dstPath, { 72 | name: name 73 | }, function(err) { 74 | return fs.unlink(dstPath, function(unlinkErr) { 75 | if (err) { 76 | console.log(unlinkErr); 77 | } 78 | return callback(err); 79 | }); 80 | }); 81 | }, 82 | resize: function(srcPath, file, name, callback) { 83 | var attachFile, buildThumb, dstPath, err, error, gmRunner; 84 | dstPath = "/tmp/2-" + file.id; 85 | try { 86 | attachFile = function(err) { 87 | if (err) { 88 | return callback(err); 89 | } else { 90 | 91 | } 92 | }; 93 | gmRunner = gm(srcPath).options({ 94 | imageMagick: true 95 | }); 96 | if (name === 'thumb') { 97 | buildThumb = function(width, height) { 98 | return gmRunner.resize(width, height).crop(300, 300, 0, 0).write(dstPath, function(err) { 99 | if (err) { 100 | return callback(err); 101 | } else { 102 | return thumb.attachFile(file, dstPath, name, callback); 103 | } 104 | }); 105 | }; 106 | return gmRunner.size(function(err, data) { 107 | if (err) { 108 | return callback(err); 109 | } else { 110 | if (data.width > data.height) { 111 | return buildThumb(null, 300); 112 | } else { 113 | return buildThumb(300, null); 114 | } 115 | } 116 | }); 117 | } else if (name === 'screen') { 118 | return gmRunner.resize(1200, 800).write(dstPath, function(err) { 119 | if (err) { 120 | return callback(err); 121 | } else { 122 | return thumb.attachFile(file, dstPath, name, callback); 123 | } 124 | }); 125 | } 126 | } catch (error) { 127 | err = error; 128 | console.log(err); 129 | return callback(err); 130 | } 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /build/server/img/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/build/server/img/error.gif -------------------------------------------------------------------------------- /build/server/locales/de.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "default user name": "Someone", 3 | "email sharing subject": "%{displayName} share the photo album \"%{name}\" with you", 4 | "file": "Album", 5 | "link file content": "Cliquez ici pour voir ce fichier", 6 | "link folder content": "Cliquez ici pour naviguer dans ce dossier", 7 | "404 headline": "The album can't be found.", 8 | "404 option a": "You entered the wrong address", 9 | "404 option separator": "or", 10 | "404 option b": "the owner of this repository deleted that folder", 11 | "404 clippy sorry": "Désolé, je n'ai pas pu trouver l'album que vous cherchez", 12 | "404 clippy contact": "Vous pouvez toujours contacter le propriétaire de ce Cozy !" 13 | }; 14 | -------------------------------------------------------------------------------- /build/server/locales/en.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "default user name": "Someone", 3 | "email sharing subject": "%{displayName} share the photo album \"%{name}\" with you", 4 | "file": "album" 5 | } 6 | ; 7 | -------------------------------------------------------------------------------- /build/server/locales/es.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "default user name": "Someone", 3 | "email sharing subject": "%{displayName} share the photo album \"%{name}\" with you", 4 | "file": "album", 5 | "link file content": "Cliquez ici pour voir ce fichier", 6 | "link folder content": "Cliquez ici pour naviguer dans ce dossier", 7 | "404 headline": "The album can't be found.", 8 | "404 option a": "You entered the wrong address", 9 | "404 option separator": "or", 10 | "404 option b": "the owner of this repository deleted that folder", 11 | "404 clippy sorry": "Désolé, je n'ai pas pu trouver l'album que vous cherchez", 12 | "404 clippy contact": "Vous pouvez toujours contacter le propriétaire de ce Cozy !" 13 | }; 14 | -------------------------------------------------------------------------------- /build/server/locales/fr.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "default user name": "Quelqu'un", 3 | "email sharing subject": "%{displayName} partage l'album photo \"%{name}\" avec vous", 4 | "file": "album", 5 | "link file content": "Cliquez ici pour voir ce fichier", 6 | "link folder content": "Cliquez ici pour naviguer dans ce dossier", 7 | "404 headline": "Impossible de trouver l'album", 8 | "404 option a": "Vous n'avez pas saisi la bonne adresse", 9 | "404 option separator": "ou bien", 10 | "404 option b": "Le propriétaire de ce dépôt a supprimé ce répertoire", 11 | "404 clippy sorry": "Désolé, je n'ai pas pu trouver l'album que vous cherchez", 12 | "404 clippy contact": "Vous pouvez toujours contacter le propriétaire de ce Cozy !" 13 | }; 14 | -------------------------------------------------------------------------------- /build/server/models/album.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Album, Photo, async, cozydb, sanitize, 3 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 4 | hasProp = {}.hasOwnProperty; 5 | 6 | cozydb = require('cozydb'); 7 | 8 | async = require('async'); 9 | 10 | Photo = require('./photo'); 11 | 12 | sanitize = function(data) { 13 | if (data.title != null) { 14 | data.title = data.title.replace(/
/g, "").replace(/
/g, "").replace(/<\/div>/g, ""); 15 | } 16 | return data.date != null ? data.date : data.date = new Date(); 17 | }; 18 | 19 | module.exports = Album = (function(superClass) { 20 | extend(Album, superClass); 21 | 22 | function Album() { 23 | return Album.__super__.constructor.apply(this, arguments); 24 | } 25 | 26 | Album.schema = { 27 | id: String, 28 | title: String, 29 | description: String, 30 | date: Date, 31 | orientation: Number, 32 | coverPicture: String, 33 | clearance: cozydb.NoSchema, 34 | folderid: String 35 | }; 36 | 37 | Album.prototype.updateAttributes = function(data) { 38 | sanitize(data); 39 | return Album.__super__.updateAttributes.apply(this, arguments); 40 | }; 41 | 42 | Album.create = function(data) { 43 | sanitize(data); 44 | return Album.__super__.constructor.create.apply(this, arguments); 45 | }; 46 | 47 | Album.listWithThumbs = function(callback) { 48 | return async.parallel([ 49 | function(cb) { 50 | return Album.request('byTitle', cb); 51 | }, function(cb) { 52 | return Photo.albumsThumbs(cb); 53 | } 54 | ], function(err, results) { 55 | var album, albums, defaultCover, defaultCovers, i, len; 56 | if (err) { 57 | return callback(err); 58 | } 59 | albums = results[0], defaultCovers = results[1]; 60 | for (i = 0, len = albums.length; i < len; i++) { 61 | album = albums[i]; 62 | defaultCover = defaultCovers[album.id]; 63 | if (defaultCover && !album.coverPicture) { 64 | album.coverPicture = defaultCover[0], album.orientation = defaultCover[1]; 65 | } 66 | } 67 | return callback(null, albums); 68 | }); 69 | }; 70 | 71 | Album.prototype.getPublicURL = function(callback) { 72 | return cozydb.api.getCozyDomain((function(_this) { 73 | return function(err, domain) { 74 | var url; 75 | if (err) { 76 | return callback(err); 77 | } 78 | url = domain + "public/photos/#albums/" + _this.id; 79 | return callback(null, url); 80 | }; 81 | })(this)); 82 | }; 83 | 84 | return Album; 85 | 86 | })(cozydb.CozyModel); 87 | -------------------------------------------------------------------------------- /build/server/models/contact.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Contact, cozydb, 3 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 4 | hasProp = {}.hasOwnProperty; 5 | 6 | cozydb = require('cozydb'); 7 | 8 | module.exports = Contact = (function(superClass) { 9 | extend(Contact, superClass); 10 | 11 | function Contact() { 12 | return Contact.__super__.constructor.apply(this, arguments); 13 | } 14 | 15 | Contact.prototype.id = String; 16 | 17 | Contact.prototype.fn = String; 18 | 19 | Contact.prototype.n = String; 20 | 21 | Contact.prototype.datapoints = cozydb.NoSchema; 22 | 23 | Contact.prototype.note = String; 24 | 25 | Contact.prototype._attachments = cozydb.NoSchema; 26 | 27 | return Contact; 28 | 29 | })(cozydb.CozyModel); 30 | -------------------------------------------------------------------------------- /build/server/models/file.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var File, cozydb, 3 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 4 | hasProp = {}.hasOwnProperty; 5 | 6 | cozydb = require('cozydb'); 7 | 8 | module.exports = File = (function(superClass) { 9 | extend(File, superClass); 10 | 11 | function File() { 12 | return File.__super__.constructor.apply(this, arguments); 13 | } 14 | 15 | File.schema = { 16 | id: String, 17 | name: String, 18 | path: String, 19 | lastModification: String, 20 | binary: cozydb.NoSchema, 21 | "class": String 22 | }; 23 | 24 | File.imageByDate = function(options, callback) { 25 | return File.request('imageByDate', options, callback); 26 | }; 27 | 28 | File.withoutThumb = function(callback) { 29 | return File.request('withoutThumb', {}, callback); 30 | }; 31 | 32 | return File; 33 | 34 | })(cozydb.CozyModel); 35 | -------------------------------------------------------------------------------- /build/server/models/photo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var Helpers, Photo, async, cozydb, fs, log, 3 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 4 | hasProp = {}.hasOwnProperty; 5 | 6 | cozydb = require('cozydb'); 7 | 8 | async = require('async'); 9 | 10 | fs = require('fs'); 11 | 12 | Helpers = require('../helpers/thumb'); 13 | 14 | log = require('printit')({ 15 | date: true, 16 | prefix: "model:photo" 17 | }); 18 | 19 | module.exports = Photo = (function(superClass) { 20 | extend(Photo, superClass); 21 | 22 | function Photo() { 23 | return Photo.__super__.constructor.apply(this, arguments); 24 | } 25 | 26 | Photo.schema = { 27 | id: String, 28 | title: String, 29 | description: String, 30 | orientation: Number, 31 | binary: cozydb.NoSchema, 32 | _attachments: Object, 33 | albumid: String, 34 | date: String, 35 | gps: Object 36 | }; 37 | 38 | Photo.fromAlbum = function(album, callback) { 39 | var params; 40 | if (album.folderid === "all") { 41 | return Photo.request('all', {}, callback); 42 | } else { 43 | params = { 44 | startkey: [album.id], 45 | endkey: [album.id + "0"] 46 | }; 47 | return Photo.request('byalbum', params, callback); 48 | } 49 | }; 50 | 51 | Photo.patchGps = function(callback) { 52 | return Photo.fromAlbum({ 53 | folderId: "all" 54 | }, function(err, photos) { 55 | if (err != null) { 56 | return callback(err); 57 | } 58 | return async.eachSeries(photos, function(photo, next) { 59 | if ((photo.binary != null) && (photo.gps == null)) { 60 | return photo.extractGpsFromBinary(next); 61 | } else { 62 | return setImmediate(next); 63 | } 64 | }, callback); 65 | }); 66 | }; 67 | 68 | Photo.prototype.extractGpsFromBinary = function(callback) { 69 | var kind, res; 70 | kind = this.binary.raw ? 'raw' : 'file'; 71 | res = this.getBinary(kind, function(err) { 72 | if (err != null) { 73 | return log.error(err); 74 | } 75 | }); 76 | return res.on('ready', (function(_this) { 77 | return function(stream) { 78 | return Helpers.readMetadata(stream, function(err, data) { 79 | if (err != null) { 80 | log.error("Error reading metadata of " + _this.id + " / " + _this.title); 81 | log.error(err); 82 | return callback(); 83 | } else { 84 | return _this.updateAttributes({ 85 | gps: data.exif.gps 86 | }, function(err) { 87 | if (err != null) { 88 | log.error(err); 89 | } 90 | return callback(); 91 | }); 92 | } 93 | }); 94 | }; 95 | })(this)); 96 | }; 97 | 98 | Photo.albumsThumbs = function(callback) { 99 | var params; 100 | params = { 101 | reduce: true, 102 | group: true 103 | }; 104 | return Photo.rawRequest('albumphotos', params, function(err, results) { 105 | var i, len, out, result; 106 | if (err) { 107 | return callback(err); 108 | } 109 | out = {}; 110 | for (i = 0, len = results.length; i < len; i++) { 111 | result = results[i]; 112 | out[result.key] = result.value; 113 | } 114 | return callback(null, out); 115 | }); 116 | }; 117 | 118 | Photo.prototype.destroyWithBinary = function(callback) { 119 | var binaries; 120 | if ((this.binary != null) && typeof this.binary === 'object') { 121 | binaries = Object.keys(this.binary); 122 | return async.eachSeries(binaries, (function(_this) { 123 | return function(bin, cb) { 124 | return _this.removeBinary(bin, function(err) { 125 | if (err) { 126 | log.error("Cannot destroy binary linked to photo " + _this.id); 127 | } 128 | return cb(); 129 | }); 130 | }; 131 | })(this), (function(_this) { 132 | return function(err) { 133 | return _this.destroy(callback); 134 | }; 135 | })(this)); 136 | } else { 137 | return this.destroy(callback); 138 | } 139 | }; 140 | 141 | return Photo; 142 | 143 | })(cozydb.CozyModel); 144 | -------------------------------------------------------------------------------- /build/server/models/requests.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | var albumPhotosRequest, allMap, byAlbumMap, byTitleMap, emit, imageByDate, withoutThumb; 3 | 4 | emit = null; 5 | 6 | allMap = function(doc) { 7 | return emit(doc._id, doc); 8 | }; 9 | 10 | byTitleMap = function(doc) { 11 | return emit(doc.title, doc); 12 | }; 13 | 14 | byAlbumMap = function(photo) { 15 | return emit([photo.albumid, photo.title], photo); 16 | }; 17 | 18 | imageByDate = function(doc) { 19 | var ref; 20 | if (doc["class"] === "image" && (((ref = doc.binary) != null ? ref.file : void 0) != null)) { 21 | return emit(doc.lastModification, doc); 22 | } 23 | }; 24 | 25 | withoutThumb = function(doc) { 26 | var ref; 27 | if (doc["class"] === "image" && (((ref = doc.binary) != null ? ref.file : void 0) != null) && (doc.binary.thumb == null)) { 28 | return emit(doc._id, doc); 29 | } 30 | }; 31 | 32 | albumPhotosRequest = { 33 | map: function(photo) { 34 | return emit(photo.albumid, [photo._id, photo.orientation]); 35 | }, 36 | reduce: function(key, values, rereduce) { 37 | return values[0]; 38 | } 39 | }; 40 | 41 | module.exports = { 42 | 'album': { 43 | 'all': allMap, 44 | 'byTitle': byTitleMap 45 | }, 46 | 'photo': { 47 | 'all': allMap, 48 | 'byalbum': byAlbumMap, 49 | 'albumphotos': albumPhotosRequest 50 | }, 51 | 'contact': { 52 | 'all': allMap 53 | }, 54 | 'file': { 55 | 'imageByDate': imageByDate, 56 | 'withoutThumb': withoutThumb 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /build/server/views/en_sharemail.js: -------------------------------------------------------------------------------- 1 | var jade = require('jade/runtime'); 2 | module.exports = function template(locals) { 3 | var buf = []; 4 | var jade_mixins = {}; 5 | var jade_interp; 6 | ;var locals_for_with = (locals || {});(function (displayEmail, displayName, doc, url) { 7 | buf.push("
" + (jade.escape((jade_interp = displayName) == null ? '' : jade_interp)) + " (" + (jade.escape((jade_interp = displayEmail) == null ? '' : jade_interp)) + ") shared an album called \"" + (jade.escape((jade_interp = doc.title) == null ? '' : jade_interp)) + "\" with you via Cozy Cloud.
View album

Sent from " + (jade.escape((jade_interp = displayName) == null ? '' : jade_interp)) + "'s Cozy.

");}.call(this,"displayEmail" in locals_for_with?locals_for_with.displayEmail:typeof displayEmail!=="undefined"?displayEmail:undefined,"displayName" in locals_for_with?locals_for_with.displayName:typeof displayName!=="undefined"?displayName:undefined,"doc" in locals_for_with?locals_for_with.doc:typeof doc!=="undefined"?doc:undefined,"url" in locals_for_with?locals_for_with.url:typeof url!=="undefined"?url:undefined));;return buf.join(""); 8 | } -------------------------------------------------------------------------------- /build/server/views/fr_sharemail.js: -------------------------------------------------------------------------------- 1 | var jade = require('jade/runtime'); 2 | module.exports = function template(locals) { 3 | var buf = []; 4 | var jade_mixins = {}; 5 | var jade_interp; 6 | ;var locals_for_with = (locals || {});(function (displayEmail, displayName, doc, url) { 7 | buf.push("
" + (jade.escape((jade_interp = displayName) == null ? '' : jade_interp)) + " (" + (jade.escape((jade_interp = displayEmail) == null ? '' : jade_interp)) + ") a partagé l'album \"" + (jade.escape((jade_interp = doc.title) == null ? '' : jade_interp)) + "\" avec vous via Cozy Cloud.
Voir Album

Envoyé depuis le Cozy de " + (jade.escape((jade_interp = displayName) == null ? '' : jade_interp)) + ".

");}.call(this,"displayEmail" in locals_for_with?locals_for_with.displayEmail:typeof displayEmail!=="undefined"?displayEmail:undefined,"displayName" in locals_for_with?locals_for_with.displayName:typeof displayName!=="undefined"?displayName:undefined,"doc" in locals_for_with?locals_for_with.doc:typeof doc!=="undefined"?doc:undefined,"url" in locals_for_with?locals_for_with.url:typeof url!=="undefined"?url:undefined));;return buf.join(""); 8 | } -------------------------------------------------------------------------------- /build/server/views/index.js: -------------------------------------------------------------------------------- 1 | var jade = require('jade/runtime'); 2 | module.exports = function template(locals) { 3 | var buf = []; 4 | var jade_mixins = {}; 5 | var jade_interp; 6 | ;var locals_for_with = (locals || {});(function (imports) { 7 | buf.push("Cozy - Photos");}.call(this,"imports" in locals_for_with?locals_for_with.imports:typeof imports!=="undefined"?imports:undefined));;return buf.join(""); 8 | } -------------------------------------------------------------------------------- /client/_specs/fakeserver.coffee: -------------------------------------------------------------------------------- 1 | createSinonServer = -> 2 | @server = server = sinon.fakeServer.create() 3 | 4 | # DRY JSON management 5 | # - method : the HTTP verb we are responding to 6 | # - url : the url we are responding to 7 | # - code : the HTTP status code to reply with 8 | # - JSONResponder : a (req, body) -> reply's body function 9 | createAutoResponse = (method, url, code, JSONResponder) -> 10 | server.respondWith method, url, (req) -> 11 | body = JSON.parse req.requestBody 12 | res = JSONResponder req, body 13 | headers = 'Content-Type': 'application/json' 14 | req.respond code, headers, JSON.stringify res 15 | 16 | # utility function to check in tests that the requests are as expected 17 | @server.checkLastRequestIs = (method, url) -> 18 | req = server.requests[server.requests.length - 1] 19 | expect(req.url).to.equal url 20 | expect(req.method).to.equal method 21 | 22 | # begin actual fake server 23 | createAutoResponse 'POST', 'albums', 200, (req, body) -> 24 | id: 'a1' 25 | title: body.title 26 | description: body.description 27 | 28 | createAutoResponse 'GET', 'albums/a1', 200, (req) -> 29 | id: 'a1' 30 | title: 'title' 31 | description: 'description' 32 | 33 | createAutoResponse 'PUT', 'albums/a1', 200, (req, body) -> 34 | id: body.id 35 | title: body.title 36 | description: body.description 37 | 38 | createAutoResponse 'DELETE', 'albums/a1', 200, (req, body) -> 39 | success: 'album deleted' 40 | 41 | return @server 42 | -------------------------------------------------------------------------------- /client/_specs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /client/_specs/lib/base_view.coffee: -------------------------------------------------------------------------------- 1 | 2 | describe 'lib/base_view', -> 3 | 4 | BaseView = require 'lib/base_view' 5 | class testView extends BaseView 6 | template: -> '
' 7 | getRenderData: -> key: 'value' 8 | 9 | options = optkey: 'optvalue' 10 | 11 | spyTemplate = sinon.spy testView.prototype, 'template' 12 | spyRenderData = sinon.spy testView.prototype, 'getRenderData' 13 | 14 | it 'should not call anything on creation', -> 15 | @view = new testView(options) 16 | expect(spyTemplate.called).to.be.false 17 | expect(spyRenderData.called).to.be.false 18 | 19 | it 'should not throw on render', -> 20 | @view.render() 21 | 22 | it 'should have called getRenderData', -> 23 | expect(spyRenderData.calledOnce).to.be.true 24 | 25 | it 'should have called template with renderData and options', -> 26 | expect(spyTemplate.calledOnce).to.be.true 27 | arg = spyTemplate.firstCall.args[0] 28 | expect(arg).to.have.property('key', 'value') 29 | expect(arg).to.have.property('optkey', 'optvalue') 30 | 31 | it 'should contains the template', -> 32 | expect(@view.$el.find '#test').to.have.length 1 33 | 34 | # TODO check for memory leaks 35 | -------------------------------------------------------------------------------- /client/_specs/lib/baseview.coffee: -------------------------------------------------------------------------------- 1 | 2 | describe 'lib/base_view', -> 3 | 4 | BaseView = require 'lib/base_view' 5 | class testView extends BaseView 6 | template: -> '
' 7 | getRenderData: -> key: 'value' 8 | 9 | options = optkey: 'optvalue' 10 | 11 | spyTemplate = sinon.spy testView.prototype, 'template' 12 | spyRenderData = sinon.spy testView.prototype, 'getRenderData' 13 | 14 | it 'should not call anything on creation', -> 15 | @view = new testView(options) 16 | expect(spyTemplate.called).to.be.false 17 | expect(spyRenderData.called).to.be.false 18 | 19 | it 'should not throw on render', -> 20 | @view.render() 21 | 22 | it 'should have called getRenderData', -> 23 | expect(spyRenderData.calledOnce).to.be.true 24 | 25 | it 'should have called template with renderData and options', -> 26 | expect(spyTemplate.calledOnce).to.be.true 27 | arg = spyTemplate.firstCall.args[0] 28 | expect(arg).to.have.property('key', 'value') 29 | expect(arg).to.have.property('optkey', 'optvalue') 30 | 31 | it 'should contains the template', -> 32 | expect(@view.$el.find '#test').to.have.length 1 33 | 34 | # TODO check for memory leaks 35 | -------------------------------------------------------------------------------- /client/_specs/lib/view_collection.coffee: -------------------------------------------------------------------------------- 1 | 2 | describe 'lib/view_collection', -> 3 | 4 | BaseView = require 'lib/base_view' 5 | ViewCollection = require 'lib/view_collection' 6 | 7 | class myModel extends Backbone.Model 8 | 9 | class myCollection extends Backbone.Collection 10 | model: myModel 11 | 12 | class myView extends BaseView 13 | className: 'item' 14 | template: -> 'item content' 15 | getRenderData: -> @model.attributes 16 | 17 | class myCollectionView extends ViewCollection 18 | itemView: myView 19 | template: -> '
' 20 | itemViewOptions: -> optkey: 'optvalue' 21 | 22 | options = optkey: 'optvalue' 23 | 24 | spyRender = sinon.spy myCollectionView.prototype, 'render' 25 | spyTemplate = sinon.spy myCollectionView.prototype, 'template' 26 | 27 | spyItemRender = sinon.spy myView.prototype, 'render' 28 | spyItemRemove = sinon.spy myView.prototype, 'remove' 29 | spyItemTemplate = sinon.spy myView.prototype, 'template' 30 | 31 | it 'should not call anything on creation', -> 32 | @collection = new myCollection() 33 | @view = new myCollectionView(collection: @collection) 34 | expect(spyTemplate.called).to.be.false 35 | expect(spyRender.called).to.be.false 36 | 37 | it 'should render a subview when I add a model to the collection', -> 38 | @model = new myModel attribute1:'value1' 39 | @collection.add @model 40 | expect(spyItemRender.calledOnce).to.be.true 41 | expect(spyItemTemplate.calledOnce).to.be.true 42 | arg = spyItemTemplate.firstCall.args[0] 43 | expect(arg).to.have.property('attribute1', 'value1') 44 | expect(arg).to.have.property('optkey', 'optvalue') 45 | expect(@view.$el.find '.item').to.have.length 1 46 | 47 | it 'should not touch subviews on render', -> 48 | @view.render() for i in [1..100] 49 | expect(spyItemRender.calledOnce).to.be.true 50 | expect(spyItemTemplate.calledOnce).to.be.true 51 | expect(@view.$el.find '#test').to.have.length 1 52 | 53 | it 'should remove the subview when I remove the model', -> 54 | @collection.remove @model 55 | expect(@view.$el.find '.item').to.have.length 0 56 | 57 | it 'should not keep a reference to the view', -> 58 | expect(_.size(@view.views)).to.equal 0 59 | expect(spyItemRemove.calledOnce).to.be.true 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /client/_specs/models/album.coffee: -------------------------------------------------------------------------------- 1 | describe 'models/album', -> 2 | 3 | Album = require 'models/album' 4 | PhotoCollection = require 'collections/photo' 5 | 6 | before -> @model = new Album() 7 | before createSinonServer 8 | after -> @server.restore() 9 | 10 | it 'should have a photos field, of type PhotoCollection', -> 11 | expect(@model.photos).to.be.instanceof PhotoCollection 12 | -------------------------------------------------------------------------------- /client/_specs/views/helpers.coffee: -------------------------------------------------------------------------------- 1 | describe 'view/helpers', -> 2 | 3 | helpers = require 'lib/helpers' 4 | 5 | describe '.limitLength', -> 6 | 7 | shortString = "test" 8 | 9 | longString = """ 10 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 11 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 12 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 13 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 14 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 15 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 16 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 17 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 18 | """ 19 | 20 | it 'should not change a short string', -> 21 | output = helpers.limitLength shortString, 50 22 | expect(output).to.equal shortString 23 | 24 | it 'should shorten a long string and finish by ...', -> 25 | output = helpers.limitLength longString, 50 26 | console.log output.length 27 | expect(output).to.have.length 53 28 | expect(output.substring 50).to.equal '...' 29 | -------------------------------------------------------------------------------- /client/app/application.coffee: -------------------------------------------------------------------------------- 1 | SocketListener = require './lib/socket_listener' 2 | 3 | module.exports = 4 | 5 | initialize: -> 6 | 7 | window.app = this 8 | # Translation helpers. 9 | @locale = window.locale 10 | @polyglot = new Polyglot locale: @locale 11 | try 12 | locales = require 'locales/'+ @locale 13 | catch e 14 | locales = require 'locales/en' 15 | 16 | @polyglot.extend locales 17 | window.t = @polyglot.t.bind @polyglot 18 | 19 | AlbumCollection = require('collections/album') 20 | Router = require('router') 21 | 22 | @router = new Router() 23 | 24 | $(window).on "hashchange", @router.hashChange 25 | $(window).on "beforeunload", @router.beforeUnload 26 | 27 | # Base data 28 | @albums = new AlbumCollection() 29 | 30 | @urlKey = "" 31 | if window.location.search 32 | for param in window.location.search.substring(1).split '&' 33 | [key, value] = param.split '=' 34 | @urlKey = "?key=#{value}" if key is 'key' 35 | 36 | @mode = if window.location.pathname.match /public/ then 'public' 37 | else 'owner' 38 | 39 | if @mode isnt 'public' 40 | # on public pages, realtime is not available, so we don't need 41 | # the socket listener, to prevent 404 42 | @socketListener = new SocketListener() 43 | 44 | # Display albums. Fetch data if no data were loaded via server index. 45 | if window.initalbums 46 | @albums.reset window.initalbums, parse: true 47 | delete window.initalbums 48 | Backbone.history.start() 49 | else 50 | @albums.fetch().done -> Backbone.history.start() 51 | -------------------------------------------------------------------------------- /client/app/assets/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/android-chrome-144x144.png -------------------------------------------------------------------------------- /client/app/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/app/assets/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/android-chrome-36x36.png -------------------------------------------------------------------------------- /client/app/assets/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/android-chrome-48x48.png -------------------------------------------------------------------------------- /client/app/assets/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/android-chrome-72x72.png -------------------------------------------------------------------------------- /client/app/assets/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/android-chrome-96x96.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /client/app/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /client/app/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #cdb19b 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/app/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/favicon-16x16.png -------------------------------------------------------------------------------- /client/app/assets/favicon-194x194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/favicon-194x194.png -------------------------------------------------------------------------------- /client/app/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/favicon-32x32.png -------------------------------------------------------------------------------- /client/app/assets/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/favicon-96x96.png -------------------------------------------------------------------------------- /client/app/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/favicon.ico -------------------------------------------------------------------------------- /client/app/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /client/app/assets/fonts/mavenpro-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/mavenpro-bold.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/mavenpro-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/mavenpro-bold.woff2 -------------------------------------------------------------------------------- /client/app/assets/fonts/mavenpro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/mavenpro-regular.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/mavenpro-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/mavenpro-regular.woff2 -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-bold-italic.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-bold-italic.woff2 -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-bold.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-bold.woff2 -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-italic.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-italic.woff2 -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-regular.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/sourcesanspro-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/fonts/sourcesanspro-regular.woff2 -------------------------------------------------------------------------------- /client/app/assets/icons/main_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/icons/main_icon.png -------------------------------------------------------------------------------- /client/app/assets/icons/main_icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/icons/main_icon.xcf -------------------------------------------------------------------------------- /client/app/assets/icons/main_icon_ico.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/icons/main_icon_ico.xcf -------------------------------------------------------------------------------- /client/app/assets/images/defaultpicture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/images/defaultpicture.png -------------------------------------------------------------------------------- /client/app/assets/images/search-icon-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/images/search-icon-mobile.png -------------------------------------------------------------------------------- /client/app/assets/images/search-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/images/search-icon.png -------------------------------------------------------------------------------- /client/app/assets/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /client/app/assets/img/cozy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/cozy-logo.png -------------------------------------------------------------------------------- /client/app/assets/img/create.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/create.gif -------------------------------------------------------------------------------- /client/app/assets/img/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/error.gif -------------------------------------------------------------------------------- /client/app/assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /client/app/assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /client/app/assets/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/loading.gif -------------------------------------------------------------------------------- /client/app/assets/img/new-galery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/new-galery.png -------------------------------------------------------------------------------- /client/app/assets/img/nophotos.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/img/nophotos.gif -------------------------------------------------------------------------------- /client/app/assets/leaflet-images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/leaflet-images/layers-2x.png -------------------------------------------------------------------------------- /client/app/assets/leaflet-images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/leaflet-images/layers.png -------------------------------------------------------------------------------- /client/app/assets/leaflet-images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/leaflet-images/marker-icon-2x.png -------------------------------------------------------------------------------- /client/app/assets/leaflet-images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/leaflet-images/marker-icon.png -------------------------------------------------------------------------------- /client/app/assets/leaflet-images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/leaflet-images/marker-shadow.png -------------------------------------------------------------------------------- /client/app/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cozy Photos", 3 | "icons": [ 4 | { 5 | "src": "\/apps\/photos\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/apps\/photos\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/apps\/photos\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/apps\/photos\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/apps\/photos\/android-chrome-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/apps\/photos\/android-chrome-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /client/app/assets/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/mstile-144x144.png -------------------------------------------------------------------------------- /client/app/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/mstile-150x150.png -------------------------------------------------------------------------------- /client/app/assets/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/mstile-310x150.png -------------------------------------------------------------------------------- /client/app/assets/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/mstile-310x310.png -------------------------------------------------------------------------------- /client/app/assets/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/client/app/assets/mstile-70x70.png -------------------------------------------------------------------------------- /client/app/collections/album.coffee: -------------------------------------------------------------------------------- 1 | # AlbumCollection 2 | # is a collection of Albums (models/album) 3 | # define the endpoint where Backbone will fetch the list of album 4 | 5 | module.exports = class AlbumCollection extends Backbone.Collection 6 | 7 | model: require 'models/album' 8 | url: 'albums' + app.urlKey 9 | comparator: (model) -> model.get 'title' 10 | -------------------------------------------------------------------------------- /client/app/collections/photo.coffee: -------------------------------------------------------------------------------- 1 | # PhotoCollection 2 | # is a collection of Photos (models/photo) 3 | # define the endpoint where Backbone will fetch the list of photos 4 | 5 | module.exports = class PhotoCollection extends Backbone.Collection 6 | 7 | model: require 'models/photo' 8 | url: -> 'photos' + app.urlKey 9 | comparator: (model) -> model.get 'title' 10 | 11 | hasGPS: ()-> 12 | return new PhotoCollection this.filter (photo)-> 13 | return photo.get("gps").lat? 14 | 15 | hasNotGPS: ()-> 16 | return new PhotoCollection this.filter (photo)-> 17 | return !photo.get("gps").lat? 18 | -------------------------------------------------------------------------------- /client/app/initialize.coffee: -------------------------------------------------------------------------------- 1 | app = require 'application' 2 | 3 | $ -> 4 | jQuery.event.props.push 'dataTransfer' 5 | app.initialize() 6 | 7 | # Initialize Spin JS the lib that displays loading indicators 8 | $.fn.spin = (opts, color) -> 9 | presets = 10 | tiny: 11 | lines: 8 12 | length: 2 13 | width: 2 14 | radius: 3 15 | 16 | small: 17 | lines: 8 18 | length: 1 19 | width: 2 20 | radius: 5 21 | 22 | large: 23 | lines: 10 24 | length: 8 25 | width: 4 26 | radius: 8 27 | 28 | if Spinner 29 | @each -> 30 | $this = $(this) 31 | spinner = $this.data("spinner") 32 | if spinner? 33 | spinner.stop() 34 | $this.data "spinner", null 35 | else if opts isnt false 36 | if typeof opts is "string" 37 | if opts of presets 38 | opts = presets[opts] 39 | else 40 | opts = {} 41 | opts.color = color if color 42 | spinner = new Spinner( 43 | $.extend(color: $this.css("color"), opts)) 44 | spinner.spin(this) 45 | $this.data "spinner", spinner 46 | 47 | else 48 | console.log "Spinner class not available." 49 | null 50 | -------------------------------------------------------------------------------- /client/app/lib/base_view.coffee: -------------------------------------------------------------------------------- 1 | # BaseView used to DRY other views 2 | # - copy view options in view object 3 | # - pass the options, as well as getRenderData to the template 4 | # - render the template inside its el 5 | # provide a afterRender hook 6 | 7 | module.exports = class BaseView extends Backbone.View 8 | initialize: (options) -> 9 | @options = options 10 | 11 | template: -> 12 | 13 | getRenderData: -> 14 | 15 | render: => 16 | data = _.extend {}, @options, @getRenderData() 17 | @$el.html @template(data) 18 | @afterRender() 19 | this 20 | 21 | afterRender: -> 22 | -------------------------------------------------------------------------------- /client/app/lib/client.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Make ajax request more easy to do. 3 | # Expected callbacks: success and error 4 | exports.request = (type, url, data, callbacks) -> 5 | 6 | success = callbacks.success or (res) -> callbacks null, res 7 | error = callbacks.error or (err) -> callbacks err 8 | 9 | 10 | $.ajax 11 | type: type 12 | url: url 13 | data: data 14 | success: success 15 | error: error 16 | 17 | # Sends a get request with data as body 18 | # Expected callbacks: success and error 19 | exports.get = (url, callbacks) -> 20 | exports.request "GET", url, null, callbacks 21 | 22 | # Sends a post request with data as body 23 | # Expected callbacks: success and error 24 | exports.post = (url, data, callbacks) -> 25 | exports.request "POST", url, data, callbacks 26 | 27 | # Sends a put request with data as body 28 | # Expected callbacks: success and error 29 | exports.put = (url, data, callbacks) -> 30 | exports.request "PUT", url, data, callbacks 31 | 32 | # Sends a delete request with data as body 33 | # Expected callbacks: success and error 34 | exports.del = (url, callbacks) -> 35 | exports.request "DELETE", url, null, callbacks 36 | -------------------------------------------------------------------------------- /client/app/lib/clipboard.coffee: -------------------------------------------------------------------------------- 1 | # Facilitator to allow user to copy url to clipboard in a easy way. 2 | module.exports = class Clipboard 3 | 4 | constructor: -> 5 | @value = "" 6 | 7 | $(document).keydown (e) => 8 | # Check if value exist and if user wants to copy 9 | if not @value or not(e.ctrlKey or e.metaKey) 10 | return 11 | if window.getSelection?()?.toString() 12 | return 13 | 14 | if document.selection?.createRange().text 15 | return 16 | _.defer => 17 | $clipboardContainer = $("#clipboard-container") 18 | $clipboardContainer.empty().show() 19 | $("") 20 | .val(@value) 21 | .appendTo($clipboardContainer) 22 | .focus() 23 | .select() 24 | 25 | $(document).keyup (e) -> 26 | if $(e.target).is("#clipboard") 27 | $("").val("") 28 | $("#clipboard-container").empty().hide() 29 | 30 | set: (value) => 31 | @value = value 32 | -------------------------------------------------------------------------------- /client/app/lib/helpers.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | # Simple shortener to display of a long text 4 | # used for album descriptions on AlbumList page 5 | # @TODO: make this HTML resistant 6 | limitLength: (string, length) -> 7 | if string? and string.length > length 8 | then string.substring(0, length) + '...' 9 | else string 10 | 11 | # Focus a given element (required for content editable elements). 12 | forceFocus: (el) -> 13 | range = document.createRange() 14 | range.selectNodeContents el[0] 15 | sel = document.getSelection() 16 | sel.removeAllRanges() 17 | sel.addRange(range) 18 | el.focus() 19 | 20 | ## Helpers to rotate photos 21 | # TODO refactor it with switch 22 | 23 | rotate: (orientation, image) -> 24 | if navigator.userAgent.search("Firefox") isnt -1 25 | transform = "transform" 26 | else 27 | transform = "-webkit-transform" 28 | if orientation is undefined or orientation is 1 29 | image.css transform, "rotate(" + 0 + "deg)" 30 | return 31 | else if orientation is 2 32 | image.css transform, "scale(-1, 1)" 33 | else if orientation is 3 34 | image.css transform, "rotate(" + 180 + "deg)" 35 | else if orientation is 4 36 | image.css transform, "scale(1, -1)" 37 | else if orientation is 5 38 | image.css transform, "rotate(" + -90 + "deg) scale(-1, 1) " 39 | else if orientation is 6 40 | image.css transform, "rotate(" + 90 + "deg)" 41 | else if orientation is 7 42 | image.css transform, "rotate(" + 90 + "deg) scale(-1, 1)" 43 | else if orientation is 8 44 | image.css transform, "rotate(" + -90 + "deg)" 45 | 46 | getRotate: (orientation, image) -> 47 | if navigator.userAgent.search("Firefox") isnt -1 48 | transform = "transform" 49 | else 50 | transform = "-webkit-transform" 51 | if orientation is undefined or orientation is 1 52 | return transform + ": rotate(" + 0 + "deg)" 53 | else if orientation is 2 54 | return transform + ": scale(-1, 1)" 55 | else if orientation is 3 56 | return transform + ": rotate(" + 180 + "deg)" 57 | else if orientation is 4 58 | return transform + ": scale(1, -1)" 59 | else if orientation is 5 60 | return transform + ": rotate(" + -90 + "deg) scale(-1, 1) " 61 | else if orientation is 6 62 | return transform + ": rotate(" + 90 + "deg)" 63 | else if orientation is 7 64 | return transform + ": rotate(" + 90 + "deg) scale(-1, 1)" 65 | else if orientation is 8 66 | return transform + ": rotate(" + -90 + "deg)" 67 | 68 | rotateLeft: (orientation, image) -> 69 | if orientation is undefined or orientation is 1 70 | return 8 71 | else if orientation is 2 72 | return 5 73 | else if orientation is 3 74 | return 6 75 | else if orientation is 4 76 | return 7 77 | else if orientation is 5 78 | return 4 79 | else if orientation is 6 80 | return 1 81 | else if orientation is 7 82 | return 2 83 | else if orientation is 8 84 | return 3 85 | 86 | rotateRight: (orientation, image) -> 87 | if orientation is undefined or orientation is 1 88 | return 6 89 | else if orientation is 2 90 | return 7 91 | else if orientation is 3 92 | return 8 93 | else if orientation is 4 94 | return 5 95 | else if orientation is 5 96 | return 2 97 | else if orientation is 6 98 | return 3 99 | else if orientation is 7 100 | return 4 101 | else if orientation is 8 102 | return 1 103 | -------------------------------------------------------------------------------- /client/app/lib/map_providers.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | This file permit to add lots of map background, you can copy-paste 3 | code from 'http://leaflet-extras.github.io/leaflet-providers/preview/' 4 | and add a nex entry in this array, the default background is the first 5 | indice. 6 | ### 7 | module.exports = 8 | 9 | 'Water color': L.tileLayer 'http://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png', 10 | attribution: 'Map by Stamen Design' 11 | subdomains: 'abcd' 12 | ext: 'png' 13 | maxZoom: 12 14 | 15 | 'Open street map hot': L.tileLayer 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', 16 | attribution: 'Map by OpenStreetMap' 17 | 18 | 'Esri world imagery': L.tileLayer 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 19 | attribution: 'Map by Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP' 20 | 21 | 'Acetate all': L.tileLayer 'http://a{s}.acetate.geoiq.com/tiles/acetate-hillshading/{z}/{x}/{y}.png', 22 | attribution: 'map by Esri & Stamen' 23 | subdomains: '0123' 24 | -------------------------------------------------------------------------------- /client/app/lib/modal.coffee: -------------------------------------------------------------------------------- 1 | BaseView = require '../lib/base_view' 2 | 3 | module.exports = class ModalView extends BaseView 4 | 5 | id: "dialog-modal" 6 | className: "modal fade" 7 | attributes: 8 | 'tab-index': -1 9 | template: require './templates/modal' 10 | value: 0 11 | 12 | events: -> 13 | "click #modal-dialog-no" : "onNo" 14 | "click #modal-dialog-yes" : "onYes" 15 | 16 | constructor: (@title, @msg, @yes, @no, @cb, @hideOnYes) -> 17 | super() 18 | @hideOnYes = true unless @hideOnYes? 19 | 20 | initialize: -> 21 | @$el.on 'hidden.bs.modal', => @close() 22 | 23 | @render() 24 | @show() 25 | 26 | onYes: -> 27 | @cb true if @cb 28 | @hide() 29 | 30 | onNo: -> 31 | @cb false if @cb 32 | @hide() if @hideOnYes 33 | 34 | onShow: -> 35 | 36 | close: -> 37 | setTimeout (() => @destroy()), 500 38 | 39 | show: -> 40 | @$el.modal 'show' 41 | @$el.trigger 'show' 42 | 43 | hide: -> 44 | @$el.modal 'hide' 45 | @$el.trigger 'hide' 46 | 47 | render: -> 48 | @$el.append @template(title: @title, msg: @msg, yes: @yes, no: @no) 49 | $("body").append @$el 50 | @afterRender() 51 | @ 52 | 53 | module.exports.error = (code, cb) -> 54 | new ModalView t("modal error"), code, t("modal ok"), false, cb 55 | -------------------------------------------------------------------------------- /client/app/lib/socket_listener.coffee: -------------------------------------------------------------------------------- 1 | contactCollection = require 'cozy-clearance/contact_collection' 2 | 3 | module.exports = class ContactListener extends CozySocketListener 4 | 5 | events: [ 6 | 'contact.create' 7 | 'contact.update' 8 | 'contact.delete' 9 | ] 10 | 11 | process: (event) -> 12 | if event.doctype is 'contact' 13 | contactCollection.handleRealtimeContactEvent event -------------------------------------------------------------------------------- /client/app/lib/view_collection.coffee: -------------------------------------------------------------------------------- 1 | BaseView = require 'lib/base_view' 2 | 3 | # View that display a collection of subitems 4 | # used to DRY views 5 | # Usage : new ViewCollection(collection:collection) 6 | # Automatically populate itself by creating a itemView for each item 7 | # in its collection 8 | 9 | # can use a template that will be displayed alongside the itemViews 10 | 11 | # itemView : the Backbone.View to be used for items 12 | # itemViewOptions : the options that will be passed to itemViews 13 | 14 | module.exports = class ViewCollection extends BaseView 15 | 16 | views: {} 17 | 18 | itemView: null 19 | 20 | 21 | itemViewOptions: -> 22 | 23 | # add 'empty' class to view when there is no subview 24 | checkIfEmpty: -> 25 | @$el.toggleClass 'empty', _.size(@views) is 0 26 | 27 | # can be overriden if we want to place the subviews somewhere else 28 | appendView: (view) -> 29 | 30 | index = @collection.indexOf view.model 31 | 32 | @$el.append view.$el 33 | # if index is 0 34 | # else 35 | 36 | # className = if view.className? then ".#{view.className}" 37 | # else "" 38 | 39 | # tagName = view.tagName or "" 40 | 41 | # selector = "#{tagName}#{className}:nth-of-type(#{index})" 42 | # @$el.find(selector).after view.$el 43 | 44 | 45 | # bind listeners to the collection 46 | initialize: -> 47 | super 48 | @views = {} 49 | @listenTo @collection, "reset", @onReset 50 | @listenTo @collection, "add", @addItem 51 | @listenTo @collection, "sort", @render 52 | @listenTo @collection, "remove", @removeItem 53 | @onReset @collection 54 | 55 | # if we have views before a render call, we detach them 56 | render: -> 57 | view.$el.detach() for id, view of @views 58 | super 59 | 60 | # after render, we reattach the views 61 | afterRender: -> 62 | if @collection.length > 0 63 | for i in [0..@collection.length-1] 64 | @appendView @views[@collection.at(i).cid] 65 | 66 | @checkIfEmpty @views 67 | 68 | # destroy all sub views before remove 69 | remove: -> 70 | @onReset [] 71 | super 72 | 73 | # event listener for reset 74 | onReset: (newcollection) -> 75 | view.remove() for id, view of @views 76 | newcollection.forEach @addItem 77 | 78 | # event listeners for add 79 | addItem: (model) => 80 | options = _.extend {}, {model: model}, @itemViewOptions(model) 81 | view = new @itemView(options) 82 | @views[model.cid] = view.render() 83 | @appendView view 84 | @checkIfEmpty @views 85 | 86 | # event listeners for remove 87 | removeItem: (model) => 88 | @views[model.cid].remove() 89 | delete @views[model.cid] 90 | @checkIfEmpty @views 91 | 92 | -------------------------------------------------------------------------------- /client/app/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "or": "or", 3 | "Back": "Back", 4 | "Create a new album": "Create a new album", 5 | "Delete": "Delete", 6 | "Download": "Download", 7 | "Edit": "Edit", 8 | "Stop editing": "Save Changes", 9 | "It will appear on your homepage.": "It will be displayed on the album page.", 10 | "Make it Hidden": "hidden", 11 | "Make it Private": "private", 12 | "Make it Public": "public", 13 | "New": "New", 14 | "private": "private", 15 | "public": "public", 16 | "hidden": "hidden", 17 | "There is no photos in this album": "There is no photo in this album. Click on Edit button to add new ones.", 18 | "There is no public albums.": "There are no albums.", 19 | "This album is private": "This album is private", 20 | "This album is hidden": "This album is hidden", 21 | "This album is public": "This album is public", 22 | "title placeholder": "Set a title for this album…", 23 | "View": "View", 24 | "description placeholder": "Write a description…", 25 | "is too big (max 10Mo)": "is too big (max 10Mo)", 26 | "is not an image": "is not an image", 27 | "Share album by mail": "Share album by mail", 28 | "Upload your contacts...": "Upload your contacts…", 29 | "Share album": "Share album", 30 | "Add contact": "Add contact", 31 | "Send mail": "Send mail", 32 | "Select your friends": "Select your friends", 33 | "Add": "Add", 34 | "Cancel": "Cancel", 35 | "photo successfully set as cover": "The picture has been successfully set as album cover", 36 | "problem occured while setting cover": "A problem occured while setting picture as cover", 37 | "pick from computer": "Click here or drag your photos below to add them to the album.", 38 | "pick from files": "Click here to pick pictures from the Files app.", 39 | "hidden-description": "It will not appear on your homepage.\nBut you can share it with the following url:", 40 | "It cannot be accessed from the public side": "It is not a public resource.", 41 | "rebuild thumbnails": "Rebuild thumbnails", 42 | "01": "January", 43 | "02": "February", 44 | "03": "March", 45 | "04": "April", 46 | "05": "May", 47 | "06": "June", 48 | "07": "July", 49 | "08": "August", 50 | "09": "September", 51 | "10": "October", 52 | "11": "November", 53 | "12": "December", 54 | "cancel": "Cancel", 55 | "copy paste link": "To give access to your contact send him/her the link below:", 56 | "details": "Details", 57 | "inherited from": "inherited from", 58 | "modal question album shareable": "Select share mode for this album", 59 | "modal shared album custom msg": "Enter email and press enter", 60 | "modal shared album link msg": "Send this link to let people access this album", 61 | "modal shared public link msg": "Send this link to let people access this folder:", 62 | "modal shared with people msg": "Invite a selection of contacts to access it. Type\nemail in the field and press enter (An email will be sent to them):", 63 | "modal send mails": "Send a notification", 64 | "modal next": "Next", 65 | "modal prev": "Previous", 66 | "modal ok": "Ok", 67 | "modal cancel": "Cancel", 68 | "modal error": "Error", 69 | "only you can see": "Only you and the people listed below can access this resource", 70 | "public": "Public", 71 | "private": "Private", 72 | "shared": "Shared", 73 | "share": "Share", 74 | "save": "Save", 75 | "see link": "See link", 76 | "send mails question": "Send a notification email to:", 77 | "sharing": "Sharing", 78 | "revoke": "Revoke", 79 | "confirm": "Confirm", 80 | "share forgot add": "Looks like you forgot to click the Add button", 81 | "share confirm save": "The changes you made to the permissions will not be saved. Do you want to continue?", 82 | "yes forgot": "Back", 83 | "no forgot": "It's ok", 84 | "perm": "can ", 85 | "perm r album": "browse this album", 86 | "perm rw album": "browse and upload photos", 87 | "mail not send": "Mail not sent", 88 | "server error occured": "Error occured on server side, please try again later", 89 | "change notif": "Check this box to be notified when a contact\nwill add a photo to this album.", 90 | "send email hint": "Notification emails will be sent one time on save", 91 | "yes": "Yes", 92 | "no": "No", 93 | "picture": "picture |||| pictures", 94 | "delete empty album": "This album is empty, do you want to delete it?", 95 | "are you sure you want to delete this album": "Are you sure you want to delete this album?", 96 | "photos search": "Loading ...", 97 | "no photos found": "No photos found", 98 | "thumb creation": "Application creates thumbs for files.", 99 | "progress": "Progression", 100 | "Navigate before upload": "Some upload are in progress, do you really want to leave this page?", 101 | "application title": "Cozy - photos", 102 | "r": "read only", 103 | "photo delete confirm": "Are you sure you want to delete this photo?" 104 | } 105 | -------------------------------------------------------------------------------- /client/app/models/album.coffee: -------------------------------------------------------------------------------- 1 | PhotoCollection = require 'collections/photo' 2 | client = require "../lib/client" 3 | 4 | # An album 5 | # Properties : 6 | # - photos : a PhotoCollection of the photo in this album 7 | # maintains attribute 8 | 9 | module.exports = class Album extends Backbone.Model 10 | 11 | urlRoot: 'albums' 12 | 13 | defaults: -> 14 | title: '' 15 | description: '' 16 | clearance: [] 17 | thumbsrc: 'img/nophotos.gif' 18 | orientation: 1 19 | updated: null 20 | 21 | url: -> super + app.urlKey 22 | 23 | constructor: -> 24 | @photos = new PhotoCollection() 25 | return super 26 | 27 | # Build orientation and cover thumb src from photo attribute/ 28 | parse: (attrs) -> 29 | 30 | if attrs.photos?.length > 0 31 | @photos.reset attrs.photos, parse: true 32 | delete attrs.photos 33 | 34 | 35 | if attrs.coverPicture and attrs.coverPicture isnt 'null' 36 | attrs.thumb = attrs.coverPicture 37 | attrs.thumbsrc = "photos/thumbs/#{attrs.coverPicture}.jpg" 38 | if @photos.get(attrs.coverPicture)?.attributes?.orientation? 39 | attrs.orientation = 40 | @photos._byId[attrs.coverPicture].attributes.orientation 41 | 42 | if attrs.clearance is 'hidden' 43 | attrs.clearance = 'public' 44 | 45 | if attrs.clearance is 'private' 46 | attrs.clearance = [] 47 | 48 | return attrs 49 | 50 | # Build cover thumb src from coverPicture field. 51 | getThumbSrc: -> 52 | coverPicture = @get 'coverPicture' 53 | if coverPicture? 54 | thumbSrc = "photos/thumbs/#{@get 'coverPicture'}.jpg" 55 | else 56 | thumbSrc = "img/nophotos.gif" 57 | return thumbSrc + app.urlKey 58 | 59 | getPublicURL: (key) -> 60 | urlKey = if key then "?key=#{key}" else "" 61 | "#{window.location.origin}/public/photos/#{urlKey}#albums/#{@id}" 62 | 63 | # Send sharing email for this album. 64 | sendMail: (url, mails, callback) -> 65 | data = 66 | url: url 67 | mails: mails 68 | client.post "albums/share", data, callback 69 | -------------------------------------------------------------------------------- /client/app/models/photo.coffee: -------------------------------------------------------------------------------- 1 | client = require('../lib/client') 2 | 3 | # A photo 4 | # maintains attributes src / thumbsrc depending of the state of the model 5 | module.exports = class Photo extends Backbone.Model 6 | 7 | defaults: -> 8 | thumbsrc: 'img/loading.gif' 9 | src: '' 10 | orientation: 1 11 | gps: {} 12 | 13 | url: -> 14 | super + app.urlKey 15 | 16 | # build img src attributes from id. 17 | parse: (attrs) -> 18 | if not attrs.id then attrs 19 | else _.extend attrs, 20 | thumbsrc: "photos/thumbs/#{attrs.id}.jpg" + app.urlKey 21 | src: "photos/#{attrs.id}.jpg" + app.urlKey 22 | orientation: attrs.orientation 23 | 24 | # Return screen size photo src built from id. 25 | getPrevSrc: -> 26 | "photos/#{@get 'id'}.jpg" 27 | 28 | Photo.listFromFiles = (page, callback)-> 29 | client.get "files/#{page}", callback 30 | 31 | Photo.makeFromFile = (fileid, attr, callback) -> 32 | client.post "files/#{fileid}/toPhoto", attr, callback 33 | -------------------------------------------------------------------------------- /client/app/models/thumbprocessor.coffee: -------------------------------------------------------------------------------- 1 | # Create an image node to get thumb prev data 2 | readFile = (photo, next) -> 3 | 4 | photo.img = new Image() 5 | photo.img.onload = -> 6 | next() 7 | photo.img.src = photo.url 8 | 9 | 10 | # resize an image into given dimensions 11 | # if fill, the image will be croped to fit in new dim 12 | resize = (photo, MAX_WIDTH, MAX_HEIGHT, fill) -> 13 | 14 | max = width: MAX_WIDTH, height: MAX_HEIGHT 15 | if (photo.img.width > photo.img.height) is fill 16 | ratiodim = 'height' 17 | else 18 | ratiodim = 'width' 19 | 20 | ratio = max[ratiodim] / photo.img[ratiodim] 21 | 22 | newdims = 23 | height: ratio * photo.img.height 24 | width: ratio * photo.img.width 25 | 26 | # use canvas to resize the image 27 | canvas = document.createElement 'canvas' 28 | canvas.width = if fill then MAX_WIDTH else newdims.width 29 | canvas.height = if fill then MAX_HEIGHT else newdims.height 30 | ctx = canvas.getContext '2d' 31 | ctx.drawImage photo.img, 0, 0, newdims.width, newdims.height 32 | return canvas.toDataURL photo.file.type 33 | 34 | # transform a dataUrl into a Blob 35 | blobify = (dataUrl, type) -> 36 | binary = atob dataUrl.split(',')[1] 37 | array = [] 38 | for i in [0..binary.length] 39 | array.push binary.charCodeAt i 40 | return new Blob [new Uint8Array(array)], type: type 41 | 42 | 43 | # create photo.thumb_du : a DataURL encoded thumbnail of photo.img 44 | makeThumbDataURI = (photo, next) -> 45 | photo.thumb_du = resize photo, 300, 300, true 46 | setTimeout next, 1 47 | 48 | # create photo.screen : a Blob(~File) copy of photo.screen_du 49 | makeThumbBlob = (photo, next) -> 50 | photo.thumb = blobify photo.thumb_du, photo.file.type 51 | setTimeout next, 1 52 | 53 | 54 | # create a FormData object with photo.file, photo.thumb, photo.screen 55 | # save the model with these files 56 | upload = (photo, next) -> 57 | formdata = new FormData() 58 | formdata.append 'thumb', photo.thumb, "thumb_#{photo.file.name}" 59 | $.ajax 60 | url: "photos/thumbs/#{photo.id}.jpg" 61 | data: formdata 62 | cache: false 63 | contentType: false 64 | processData: false 65 | type: 'PUT', 66 | success: (data) -> 67 | $("#rebuild-th").append "

#{photo.file.name} photo updated.

" 68 | 69 | 70 | 71 | # make thumb and upload 72 | uploadWorker = (photo, done) -> 73 | async.waterfall [ 74 | (cb) -> readFile photo, cb 75 | (cb) -> makeThumbDataURI photo, cb 76 | (cb) -> makeThumbBlob photo, cb 77 | (cb) -> upload photo, cb 78 | (cb) -> 79 | delete photo.img 80 | delete photo.thumb 81 | delete photo.thumb_du 82 | cb() 83 | ], (err) -> 84 | done(err) 85 | 86 | 87 | class ThumbProcessor 88 | 89 | # upload 2 by 2 90 | uploadQueue: async.queue uploadWorker, 2 91 | 92 | process: (model) -> 93 | 94 | photo = 95 | url: model.getPrevSrc() 96 | id: model.get 'id' 97 | file: 98 | type: 'image/jpeg' 99 | name: model.get 'title' 100 | 101 | #setTimeout => 102 | #@uploadQueue.push photo, (err) => 103 | #return console.log err if err 104 | #, 300 105 | uploadWorker photo, (err) -> 106 | console.log err if err 107 | 108 | 109 | module.exports = new ThumbProcessor() 110 | -------------------------------------------------------------------------------- /client/app/router.coffee: -------------------------------------------------------------------------------- 1 | app = require 'application' 2 | AlbumsListView = require 'views/albumslist' 3 | AlbumView = require 'views/album' 4 | MapView = require 'views/map' 5 | Album = require 'models/album' 6 | PhotoCollection = require 'collections/photo' 7 | AlbumCollection = require 'collections/album' 8 | 9 | module.exports = class Router extends Backbone.Router 10 | routes: 11 | '' : 'albumslist' 12 | 'albums' : 'albumslist' 13 | 'albums/edit' : 'albumslistedit' 14 | 'albums/new' : 'newalbum' 15 | 'albums/:albumid' : 'album' 16 | 'albums/:albumid/edit': 'albumedit' 17 | 'albums/:albumid/photo/:photoid': 'photo' 18 | 'albums/:albumid/edit/photo/:photoid': 'photoedit' 19 | #MODIF:remi 20 | 'map': 'showmap' 21 | 22 | # display the "map" page 23 | showmap: -> 24 | allphotos = new PhotoCollection 25 | allphotos.fetch {reset:true} 26 | 27 | @displayView new MapView 28 | collection: allphotos 29 | 30 | # display the "home" page : list of albums 31 | albumslist: (editable=false)-> 32 | @displayView new AlbumsListView 33 | collection: app.albums.sort() 34 | editable: editable 35 | 36 | # display the list of albums in edit mode 37 | albumslistedit: -> 38 | return @navigate 'albums', true if app.mode is 'public' 39 | @albumslist true 40 | 41 | # display the album view for an album with given id 42 | # fetch before displaying it 43 | album: (id, editable=false, callback) -> 44 | if @mainView?.model?.get('id') is id 45 | 46 | if editable 47 | @mainView.makeEditable() 48 | else 49 | @mainView.makeNonEditable() 50 | 51 | if callback 52 | callback() 53 | else 54 | @mainView.closeGallery() 55 | 56 | else 57 | album = app.albums.get(id) or new Album id: id 58 | album.fetch() 59 | .done => 60 | @displayView new AlbumView 61 | model: album 62 | editable: editable 63 | 64 | if callback 65 | callback() 66 | else 67 | @mainView.closeGallery() 68 | 69 | .fail => 70 | alert t 'this album does not exist' 71 | @navigate 'albums', true 72 | 73 | 74 | # Display given photo from given album (non edit mode). 75 | photo: (albumid, photoid) -> 76 | @album albumid, false, => 77 | @mainView.showPhoto photoid 78 | 79 | 80 | # Display given photo from given album (edit mode). 81 | photoedit: (albumid, photoid) -> 82 | @album albumid, true, => 83 | @mainView.showPhoto photoid 84 | 85 | 86 | # display the album view in edit mode 87 | albumedit: (id) -> 88 | return @navigate 'albums', true if app.mode is 'public' 89 | @album id, true 90 | setTimeout -> 91 | $('#title').focus() 92 | , 200 93 | 94 | # display the album view for a new Album 95 | newalbum: -> 96 | return @navigate 'albums', true if app.mode is 'public' 97 | window.app.albums.create {}, 98 | success: (model) => 99 | @navigate "albums/#{model.id}/edit", true 100 | error: => 101 | @navigate "albums", true 102 | 103 | # display a page properly (remove previous page) 104 | displayView: (view) => 105 | @mainView.remove() if @mainView 106 | @mainView = view 107 | 108 | el = @mainView.render().$el 109 | el.addClass "mode-#{app.mode}" 110 | $('body').append el 111 | 112 | hashChange: (event) => 113 | if @cancelNavigate 114 | event.stopImmediatePropagation() 115 | @cancelNavigate = false 116 | else 117 | 118 | # default window title 119 | document.title = t 'application title' 120 | 121 | if @mainView and @mainView.dirty 122 | if not(window.confirm t("Navigate before upload")) 123 | event.stopImmediatePropagation() 124 | @cancelNavigate = true 125 | window.location.href = event.originalEvent.oldURL 126 | else 127 | @mainView.dirty = false 128 | 129 | beforeUnload: (event) => 130 | if @mainView and @mainView.dirty 131 | # Chrome will display this message, Firefox won't 132 | confirm = t("Navigate before upload") 133 | else 134 | confirm = undefined 135 | event.returnValue = confirm 136 | return confirm 137 | -------------------------------------------------------------------------------- /client/app/styles/_cozy.styl: -------------------------------------------------------------------------------- 1 | orange = #fe8800 2 | blue = #34A6FF 3 | bluegreen = #34A6BB 4 | green = #34a6ff 5 | 6 | 7 | a 8 | color: blue 9 | 10 | a:hover 11 | color: orange 12 | 13 | input 14 | select 15 | textarea 16 | border: 1px solid blue 17 | border-radius: 0 18 | font-family: 'Maven Pro', sans-serif 19 | 20 | input:focus 21 | select:focus 22 | textarea:focus 23 | border: 1px solid blue 24 | 25 | input:hover 26 | select:hover 27 | textarea:hover 28 | border: 1px solid orange 29 | 30 | input:active 31 | select:active 32 | textarea:active 33 | box-shadow: none 34 | 35 | 36 | .button 37 | .btn-cozy 38 | border: 0 39 | background: blue 40 | color: white 41 | box-shadow: none 42 | text-shadow: none 43 | border-radius: 0 44 | font-family: 'Source Sans Pro', sans-serif 45 | font-weight: bold 46 | padding: 10px 47 | cursor: pointer 48 | border-radius: 5px 49 | 50 | &:hover 51 | border: 0 52 | background: orange 53 | border: 0 54 | color: white 55 | text-decoration: none 56 | 57 | &:active 58 | border: 0 59 | background: orange - 3% 60 | border: 0 61 | box-shadow: 1px 3px 2px rgba(0, 0, 0, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.0) 62 | color: white 63 | outline: 0 64 | 65 | &:focus 66 | outline: 0 67 | 68 | .button.disabled 69 | .button.disabled:hover 70 | .button.disabled:active 71 | .button.disabled:focus 72 | background-color: grey 73 | padding: 10px 74 | cursor: default 75 | text-decoration: none 76 | 77 | 78 | .button.toggled 79 | background-color: blue - 20% 80 | box-shadow: 1px 3px 2px rgba(0, 0, 0, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.0) 81 | 82 | .minor-button 83 | padding: 10px 84 | 85 | 86 | .modal-header 87 | font-family: 'Maven Pro', sans-serif 88 | font-size: 26px 89 | padding: 20px 90 | border-top-left-radius: 5px 91 | border-top-right-radius: 5px 92 | 93 | .modal-body 94 | font-family: 'Source Sans Pro', sans-serif 95 | 96 | input 97 | select 98 | textarea 99 | border: 1px solid #CCC 100 | border-radius: 0 101 | font-family: 'Maven Pro', sans-serif 102 | 103 | &:hover 104 | border: 1px solid orange 105 | 106 | &:focus 107 | border: 1px solid blue 108 | box-shadow: none 109 | 110 | &:active 111 | border: 1px solid #CCC 112 | Box-shadow: none 113 | 114 | #files-browser-modal 115 | .modal-body 116 | overflow-y: auto; 117 | 118 | .modal-footer 119 | span.pull-left 120 | font-size: 12px 121 | margin-top: 10px 122 | 123 | button 124 | font-family: 'Maven Pro', sans-serif 125 | 126 | p.disabled 127 | color: #999 128 | 129 | #public-url.disabled 130 | color: #DDD 131 | background: #DDD 132 | cursor: default 133 | user-select: none 134 | 135 | &:hover 136 | &:active 137 | &:focus 138 | border: 1px solid #CCC 139 | -------------------------------------------------------------------------------- /client/app/templates/album.jade: -------------------------------------------------------------------------------- 1 | div#about 2 | div.clearfix 3 | div#links.clearfix 4 | p.back 5 | a.flatbtn(href="#albums") 6 | span.fa.fa-arrow-left.icon-white 7 | span= t("Back") 8 | p.startediting 9 | a.flatbtn(href="#albums/#{id}/edit") 10 | span.fa.fa-edit.icon-white 11 | span= t("Edit") 12 | p.stopediting 13 | a.flatbtn.stopediting(href="#albums/#{id}") 14 | span.fa.fa-save.icon-white 15 | span= t("Stop editing") 16 | p.download 17 | a.flatbtn(href="#{downloadPath}") 18 | span.fa.fa-download.icon-white 19 | span= t("Download") 20 | p.delete 21 | a.flatbtn.delete 22 | span.fa.fa-trash.icon-white 23 | span= t("Delete") 24 | p.clearance 25 | a.flatbtn.clearance 26 | span.fa.fa-share-alt.icon-white 27 | span= t("share") 28 | div#album-text 29 | div#album-text-background 30 | 31 | div.right 32 | p 33 | span.photo-number= photosNumber 34 | br 35 | span.photo-count= t("picture", {smart_count: photosNumber}) 36 | 37 | form.left 38 | div 39 | span.clearance-state 40 | if clearance == 'public' 41 | span.fa.fa-globe 42 | |   43 | | #{t('shared')} 44 | else if clearance && clearance.length > 0 45 | span.fa.fa-share-alt 46 | |   47 | | #{t('shared')} 48 | if clearance.length 49 | span  (#{clearance.length}) 50 | input#title(type="text", placeholder="#{t('title placeholder')}", value=title) 51 | textarea#description(placeholder="#{t('description placeholder')}")!= description 52 | div#publicDesc= description 53 | 54 | div#photos.clearfix 55 | //-div#rebuild-th 56 | //-a#rebuild-th-btn= t("rebuild thumbnails") 57 | -------------------------------------------------------------------------------- /client/app/templates/albumlist.jade: -------------------------------------------------------------------------------- 1 | a.create#create-album-link(href="#albums/new") 2 | span= t('Create a new album') 3 | 4 | a.create.map-link(href="#map") 5 | i.fa.fa-globe 6 | 7 | p.help= t('There is no public albums.') 8 | -------------------------------------------------------------------------------- /client/app/templates/albumlist_item.jade: -------------------------------------------------------------------------------- 1 | a(class=isRecent id="#{id}", href="#albums/#{id}") 2 | img(src="#{thumbsrc}") 3 | span.title 4 | if folderid == "all" 5 | = title 6 | else 7 | b= title 8 | -------------------------------------------------------------------------------- /client/app/templates/browser.jade: -------------------------------------------------------------------------------- 1 | .files 2 | if dates.length === 0 3 | p= t("photos search") 4 | else if dates === "No photos found" 5 | p= t("no photos found") 6 | else 7 | nav: ul 8 | if hasPrev 9 | li: a.btn.btn-cozy.prev 10 | i.fa.fa-angle-left 11 | span #{t('modal prev')} 12 | if hasNext 13 | li: a.btn.btn-cozy.next 14 | span #{t('modal next')} 15 | i.fa.fa-angle-right 16 | dl 17 | for date in dates 18 | dt #{t(date.split('-')[1])} #{date.split('-')[0]} 19 | dd: ul.thumbs 20 | for photo in photos[date] 21 | li: img(src="files/thumbs/#{photo.id}.jpg", id="#{photo.id}") 22 | nav: ul 23 | if hasPrev 24 | li: a.btn.btn-cozy.prev 25 | i.fa.fa-angle-left 26 | span #{t('modal prev')} 27 | if hasNext 28 | li: a.btn.btn-cozy.next 29 | span #{t('modal next')} 30 | i.fa.fa-angle-right 31 | -------------------------------------------------------------------------------- /client/app/templates/galery.jade: -------------------------------------------------------------------------------- 1 | p.help= t('There is no photos in this album') 2 | 3 | #upload-actions 4 | #upload-block.flatbtn 5 | input#uploader(type="file", multiple="multiple") 6 | div.pa2 7 | = t('pick from computer') 8 | 9 | #browse-files.flatbtn 10 | div.pa2 11 | = t('pick from files') 12 | -------------------------------------------------------------------------------- /client/app/templates/map.jade: -------------------------------------------------------------------------------- 1 | div(style="position: fixed; height:100%; width: 100%;") 2 | div#links(style="margin-bottom:0;") 3 | a.flatbtn(href="#") 4 | span.fa.fa-arrow-left.icon-white 5 | span= t("Back") 6 | div#map(style="height: 95%;") 7 | 8 | form.choice-box(style="position: fixed; bottom: 0; width: 100%; height:0; background-color: white; transisiton: all 0.5 linear") 9 | div#map-galery.col-sm-10(style="overflow-x: scroll; padding: 0; white-space: nowrap;") 10 | div.map-setter.col-sm-2(style="padding: 0;") 11 | div.clearfix 12 | div#links.clearfix 13 | p.back 14 | button.flatbtn#validate 15 | span= t("Apply") 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/app/templates/photo.jade: -------------------------------------------------------------------------------- 1 | a(href="#{src}", title="#{title}") 2 | img(data-src="#{thumbsrc}", alt="#{title}") 3 | div.progressfill 4 | 5 | button.delete.flatbtn 6 | i.fa.fa-times.icon-white 7 | -------------------------------------------------------------------------------- /client/app/views/albumslist.coffee: -------------------------------------------------------------------------------- 1 | ViewCollection = require 'lib/view_collection' 2 | app = require 'application' 3 | 4 | # "Home" view : the list of album 5 | # simple ViewCollection 6 | # pass editable options to children 7 | 8 | module.exports = class AlbumsList extends ViewCollection 9 | id: 'album-list' 10 | itemView: require 'views/albumslist_item' 11 | template: require 'templates/albumlist' 12 | 13 | initialize: -> 14 | super 15 | 16 | checkIfEmpty: => 17 | @$('.help').toggle _.size(@views) is 0 and app.mode is 'public' 18 | 19 | afterRender: -> 20 | super 21 | -------------------------------------------------------------------------------- /client/app/views/albumslist_item.coffee: -------------------------------------------------------------------------------- 1 | BaseView = require 'lib/base_view' 2 | {limitLength} = require 'lib/helpers' 3 | helpers = require 'lib/helpers' 4 | 5 | # Item View for the albums list 6 | module.exports = class AlbumItem extends BaseView 7 | className: 'albumitem' 8 | template: require 'templates/albumlist_item' 9 | 10 | initialize: -> 11 | @listenTo @model, 'change', => @render() 12 | 13 | getRenderData: -> 14 | out = _.clone @model.attributes 15 | out.description = limitLength out.description, 250 16 | out.thumbsrc = @model.getThumbSrc() 17 | # Album is recent if it has been updated in less than 60s 18 | out.isRecent = if (out.updated? and out.updated - Date.now() < 60000) then 'recent' else '' 19 | return out 20 | 21 | afterRender: -> 22 | @image = @$ 'img' 23 | @image.attr 'src', @model.getThumbSrc() 24 | helpers.rotate @model.attributes.orientation, @image 25 | # remove loading background one image is loaded 26 | if @image.get(0).complete 27 | @onImageLoaded() 28 | else 29 | @image.on 'load', => 30 | @onImageLoaded() 31 | 32 | onImageLoaded: -> 33 | @image.addClass 'loaded' 34 | -------------------------------------------------------------------------------- /client/app/views/browser.coffee: -------------------------------------------------------------------------------- 1 | Modal = require 'cozy-clearance/modal' 2 | Photo = require '../models/photo' 3 | 4 | 5 | module.exports = class FilesBrowser extends Modal 6 | 7 | id: 'files-browser-modal' 8 | template_content: require '../templates/browser' 9 | title: t 'pick from files' 10 | content: '

Loading ...

' 11 | 12 | events: -> _.extend super, 13 | 'click img': 'toggleSelected' 14 | 'click a.next': 'displayNextPage' 15 | 'click a.prev': 'displayPrevPage' 16 | 17 | toggleSelected: (e) -> 18 | $el = $(e.target) 19 | # Get the index of the selected element in the list 20 | index = $el.parent().index() 21 | # If shiftKey is pressed, then select the whole range of items 22 | # between the last selected element and the current one 23 | if @lastSelectedIndex? and e.shiftKey 24 | if index > @lastSelectedIndex 25 | first = @lastSelectedIndex 26 | last = index 27 | else if index < @lastSelectedIndex 28 | first = index 29 | last = @lastSelectedIndex 30 | # Filter all elements to get those in range and select it too 31 | @$('.thumbs li') 32 | .filter (index) -> 33 | return first <= index <= last 34 | .find('img').addClass 'selected' 35 | else 36 | $el.toggleClass 'selected' 37 | @lastSelectedIndex = index 38 | 39 | getRenderData: -> @options 40 | 41 | initialize: (options) -> 42 | @yes = t 'modal ok' 43 | @no = t 'modal cancel' 44 | 45 | # stores index of the last selected element for range select w/ shiftKey 46 | @lastSelectedIndex = null 47 | 48 | # Prepare option 49 | if not options.page? 50 | super {} 51 | 52 | if not options.page? 53 | options.page = 0 54 | 55 | if not options.selected? 56 | @options.selected = [] 57 | 58 | @options.page = options.page 59 | 60 | # Recover files 61 | Photo.listFromFiles options.page, (err, body) => 62 | dates = body.files if body?.files? 63 | 64 | if err 65 | return console.log err 66 | 67 | # If there is no photos in Cozy 68 | else if dates? and Object.keys(dates).length is 0 69 | @options.dates = "No photos found" 70 | 71 | else 72 | # Add next/prev button 73 | @options.hasNext = body.hasNext if body?.hasNext? 74 | @options.hasPrev = options.page isnt 0 75 | @options.dates = Object.keys dates 76 | @options.dates.sort (a, b) -> 77 | -1 * a.localeCompare b 78 | @options.photos = dates 79 | 80 | @$('.modal-body').html @template_content @getRenderData() 81 | @$('.modal-body').scrollTop(0) 82 | # Add selected files 83 | if @options.selected[@options.page]? 84 | for img in @options.selected[@options.page] 85 | @$("##{img.id}").toggleClass 'selected' 86 | 87 | 88 | cb: (confirmed) -> 89 | return unless confirmed 90 | @options.beforeUpload (attrs) => 91 | # flatten selection of photos accross pages 92 | @options.selected[@options.page] = @$('.selected') 93 | sel = [].concat.apply [], @options.selected.map (jq) -> jq.get() 94 | 95 | addImageToCollection = (img) -> 96 | attrs.title = img.name 97 | # creates a tmp version with the selected image from the browser 98 | photo = new Photo attrs 99 | photo.file = img 100 | # in the mean time, trigger an update to the server to get the 101 | # final version of the image 102 | Photo.makeFromFile img.id, attrs, (err, attributes) -> 103 | if err 104 | console.error err 105 | else 106 | photo.save attributes 107 | return photo 108 | 109 | # each (tmp) files processed, bulk-add them to the collection 110 | @collection.add (addImageToCollection img for img in sel) 111 | 112 | 113 | displayNextPage: -> 114 | # Display next page: store selected files 115 | @options.selected[@options.page] = @$('.selected') 116 | options = 117 | page: @options.page + 1 118 | selected: @options.selected 119 | @initialize options 120 | 121 | displayPrevPage: -> 122 | # Display prev page: store selected files 123 | @options.selected[@options.page] = @$('.selected') 124 | options = 125 | page: @options.page - 1 126 | selected: @options.selected 127 | @initialize options 128 | 129 | -------------------------------------------------------------------------------- /client/app/views/photo.coffee: -------------------------------------------------------------------------------- 1 | BaseView = require 'lib/base_view' 2 | helpers = require 'lib/helpers' 3 | 4 | transitionendEvents = [ 5 | "transitionend", "webkitTransitionEnd", "oTransitionEnd", "MSTransitionEnd" 6 | ].join(" ") 7 | 8 | # View for a single photo 9 | # Expected options {model, editable} 10 | module.exports = class PhotoView extends BaseView 11 | template: require 'templates/photo' 12 | className: 'photo' 13 | 14 | initialize: (options) -> 15 | super 16 | @listenTo @model, 'progress', @onProgress 17 | @listenTo @model, 'thumbed', @onThumbed 18 | @listenTo @model, 'upError', @onError 19 | @listenTo @model, 'uploadComplete', @onServer 20 | @listenTo @model, 'change', => @render() 21 | 22 | events: -> 23 | 'click' : 'onClickListener' 24 | 'click .delete' : 'destroyModel' 25 | 26 | getRenderData: -> @model.attributes 27 | 28 | afterRender: -> 29 | @link = @$ 'a' 30 | @image = @$ 'img' 31 | @progressbar = @$ '.progressfill' 32 | helpers.rotate @model.get('orientation'), @image 33 | @link.addClass 'server' unless @model.isNew() 34 | 35 | # Images are not loaded by default, they are only loaded when the 36 | # user reaches them by scrolling the window. 37 | @image.unveil() 38 | 39 | if @image.get(0).complete 40 | @onImageLoaded() 41 | else 42 | @image.on 'load', => 43 | @onImageLoaded() 44 | 45 | # Force display of current image by setting its source attribute. 46 | setSource: -> 47 | source = @$("img").attr "data-src" 48 | @$("img").attr "src", source 49 | 50 | # Change progress bar state to show the progress of the upload. 51 | setProgress: (percent) -> 52 | @progressbar.css 'width', percent + '%' 53 | 54 | # when the upload progresses 55 | onProgress: (event) -> 56 | @setProgress 10 + 90 * event.loaded / event.total 57 | 58 | # when the thumb is ready, it is displayed. 59 | onThumbed: -> 60 | @setProgress 10 61 | @image.attr 'src', @model.thumb_du 62 | @image.attr 'orientation', @model.get('orientation') 63 | @image.addClass 'thumbed' 64 | 65 | # when the upload is complete, the view of the photo is refreshed via a 66 | # remove/add. 67 | onServer: -> 68 | # detach-reatach so photobox can pick up the object 69 | preload = new Image() 70 | preload.onerror = preload.onload = => 71 | @render() 72 | 73 | preload.src = "photos/thumbs/#{@model.id}.jpg" 74 | # image loaded, we can navigate away 75 | app.router.mainView.dirty = false 76 | 77 | # when an error occured, the photo is marked with error image. 78 | onError: (err) -> 79 | @setProgress 0 80 | @error = @model.get('title') + " " + err 81 | @link.attr 'title', @error 82 | @image.attr 'src', 'img/error.gif' 83 | 84 | # Prevent opening the gallery if the photos 85 | # hasn't been upload yet 86 | onClickListener: (evt) => 87 | 88 | if @model.isNew() 89 | alert @error if @error 90 | evt.stopPropagation() 91 | evt.preventDefault() 92 | return false 93 | 94 | destroyModel: -> 95 | @$('.delete').html '    ' 96 | @$('.delete').spin 'small' 97 | @$el.fadeOut => 98 | @model.destroy 99 | success: => 100 | @collection.remove @model 101 | @remove() 102 | 103 | onImageLoaded: -> 104 | @image.addClass 'loaded' 105 | -------------------------------------------------------------------------------- /client/config.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | 3 | console.log path.resolve __dirname, '../build/client/public' 4 | 5 | exports.config = 6 | 7 | plugins: 8 | coffeelint: 9 | options: 10 | indentation: value:4, level:'error' 11 | jade: 12 | globals: ['t'] 13 | 14 | conventions: 15 | vendor: /(vendor)|(_specs)(\/|\\)/ # do not wrap tests in modules 16 | files: 17 | javascripts: 18 | defaultExtension: 'coffee' 19 | joinTo: 20 | 'javascripts/app.js': /^app/ 21 | 'javascripts/vendor.js': /^vendor/ 22 | '../_specs/specs.js': /^_specs.*\.coffee$/ 23 | order: 24 | before: [ 25 | # Backbone 26 | 'vendor/scripts/jquery-1.9.1.js', 27 | 'vendor/scripts/underscore.js', 28 | 'vendor/scripts/backbone.js', 29 | # Photobox 30 | 'vendor/scripts/photobox.js', 31 | # Async 32 | 'vendor/scripts/async.js', 33 | # Twitter Bootstrap jquery plugins 34 | 'vendor/scripts/bootstrap.js', 35 | 'vendor/scripts/polyglot.js', 36 | 'vendor/scripts/leaflet.js' 37 | ] 38 | after: [ 39 | ] 40 | stylesheets: 41 | defaultExtension: 'styl' 42 | joinTo: 43 | 'stylesheets/app.css': /^app/ 44 | 'stylesheets/vendor.css': /^vendor/ 45 | order: 46 | before: [ 47 | 'vendor/styles/bootstrap.min.css' 48 | ] 49 | after: [] 50 | templates: 51 | defaultExtension: 'jade' 52 | joinTo: 'javascripts/app.js' 53 | 54 | framework: 'backbone' 55 | 56 | overrides: 57 | production: 58 | # re-enable when uglifyjs will handle properly in source maps 59 | # with sourcesContent attribute 60 | # optimize: false 61 | sourceMaps: true 62 | paths: 63 | public: path.resolve __dirname, '../build/client/public' 64 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "javascript-brunch": "1.7.1", 4 | "css-brunch": "1.7.0", 5 | "coffee-script-brunch": "1.8.0", 6 | "stylus-brunch": "1.8.0", 7 | "jade-brunch": "1.8.1", 8 | "digest-brunch": "1.2.2", 9 | "clean-css-brunch": "1.7.1", 10 | "json-brunch": "^1.5.4" 11 | }, 12 | "devDependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /client/vendor/scripts/jquery.unveil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery Unveil 3 | * A very lightweight jQuery plugin to lazy load images 4 | * http://luis-almeida.github.com/unveil 5 | * 6 | * Licensed under the MIT license. 7 | * Copyright 2013 Luís Almeida 8 | * https://github.com/luis-almeida 9 | */ 10 | 11 | ;(function($) { 12 | 13 | $.fn.unveil = function(threshold, callback) { 14 | 15 | var $w = $(window), 16 | th = threshold || 0, 17 | retina = window.devicePixelRatio > 1, 18 | attrib = retina? "data-src-retina" : "data-src", 19 | images = this, 20 | loaded; 21 | 22 | this.one("unveil", function() { 23 | var source = this.getAttribute(attrib); 24 | source = source || this.getAttribute("data-src"); 25 | if (source) { 26 | this.setAttribute("src", source); 27 | if (typeof callback === "function") callback.call(this); 28 | } 29 | }); 30 | 31 | function unveil() { 32 | var inview = images.filter(function() { 33 | var $e = $(this); 34 | if ($e.is(":hidden")) return; 35 | 36 | var wt = $w.scrollTop(), 37 | wb = wt + $w.height(), 38 | et = $e.offset().top, 39 | eb = et + $e.height(); 40 | 41 | return eb >= wt - th && et <= wb + th; 42 | }); 43 | 44 | loaded = inview.trigger("unveil"); 45 | images = images.not(loaded); 46 | } 47 | 48 | $w.on("scroll.unveil resize.unveil lookup.unveil", unveil); 49 | 50 | unveil(); 51 | 52 | return this; 53 | 54 | }; 55 | 56 | })(window.jQuery || window.Zepto); 57 | -------------------------------------------------------------------------------- /client/vendor/styles/MarkerCluster.Default.css: -------------------------------------------------------------------------------- 1 | .marker-cluster-small { 2 | background-color: rgba(181, 226, 140, 0.6); 3 | } 4 | .marker-cluster-small div { 5 | background-color: rgba(110, 204, 57, 0.6); 6 | } 7 | 8 | .marker-cluster-medium { 9 | background-color: rgba(241, 211, 87, 0.6); 10 | } 11 | .marker-cluster-medium div { 12 | background-color: rgba(240, 194, 12, 0.6); 13 | } 14 | 15 | .marker-cluster-large { 16 | background-color: rgba(253, 156, 115, 0.6); 17 | } 18 | .marker-cluster-large div { 19 | background-color: rgba(241, 128, 23, 0.6); 20 | } 21 | 22 | /* IE 6-8 fallback colors */ 23 | .leaflet-oldie .marker-cluster-small { 24 | background-color: rgb(181, 226, 140); 25 | } 26 | .leaflet-oldie .marker-cluster-small div { 27 | background-color: rgb(110, 204, 57); 28 | } 29 | 30 | .leaflet-oldie .marker-cluster-medium { 31 | background-color: rgb(241, 211, 87); 32 | } 33 | .leaflet-oldie .marker-cluster-medium div { 34 | background-color: rgb(240, 194, 12); 35 | } 36 | 37 | .leaflet-oldie .marker-cluster-large { 38 | background-color: rgb(253, 156, 115); 39 | } 40 | .leaflet-oldie .marker-cluster-large div { 41 | background-color: rgb(241, 128, 23); 42 | } 43 | 44 | .marker-cluster { 45 | background-clip: padding-box; 46 | border-radius: 20px; 47 | } 48 | .marker-cluster div { 49 | width: 30px; 50 | height: 30px; 51 | margin-left: 5px; 52 | margin-top: 5px; 53 | 54 | text-align: center; 55 | border-radius: 15px; 56 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; 57 | } 58 | .marker-cluster span { 59 | line-height: 30px; 60 | } 61 | -------------------------------------------------------------------------------- /client/vendor/styles/MarkerCluster.css: -------------------------------------------------------------------------------- 1 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow { 2 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in; 3 | -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in; 4 | -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in; 5 | transition: transform 0.3s ease-out, opacity 0.3s ease-in; 6 | } 7 | -------------------------------------------------------------------------------- /client/vendor/styles/leaflet-search.css: -------------------------------------------------------------------------------- 1 | 2 | .leaflet-container .leaflet-control-search { 3 | position:relative; 4 | float:left; 5 | background:#fff; 6 | color:#1978cf; 7 | -moz-border-radius: 4px; 8 | -webkit-border-radius: 4px; 9 | border-radius: 4px; 10 | background-color: rgba(255, 255, 255, 0.8); 11 | z-index:1000; 12 | box-shadow: 0 1px 7px rgba(0,0,0,0.65); 13 | margin-left: 10px; 14 | margin-top: 10px; 15 | } 16 | .leaflet-control-search.search-exp {/*expanded*/ 17 | box-shadow: 0 1px 7px #999; 18 | background: #fff; 19 | } 20 | .leaflet-control-search .search-input { 21 | display:block; 22 | float:left; 23 | background: #fff; 24 | border:1px solid #666; 25 | border-radius:2px; 26 | height:18px; 27 | padding:0 18px 0 2px; 28 | margin:3px 0 3px 3px; 29 | } 30 | .leaflet-control-search.search-load .search-input { 31 | background: url('../images/loader.gif') no-repeat center right #fff; 32 | } 33 | .leaflet-control-search.search-load .search-cancel { 34 | visibility:hidden; 35 | } 36 | .leaflet-control-search .search-cancel { 37 | display:block; 38 | width:22px; 39 | height:18px; 40 | position:absolute; 41 | right:22px; 42 | margin:3px 0; 43 | background: url('../images/search-icon.png') no-repeat 0 -46px; 44 | text-decoration:none; 45 | filter: alpha(opacity=80); 46 | opacity: 0.8; 47 | } 48 | .leaflet-control-search .search-cancel:hover { 49 | filter: alpha(opacity=100); 50 | opacity: 1; 51 | } 52 | .leaflet-control-search .search-cancel span { 53 | display:none;/* comment for cancel button imageless */ 54 | font-size:18px; 55 | line-height:20px; 56 | color:#ccc; 57 | font-weight:bold; 58 | } 59 | .leaflet-control-search .search-cancel:hover span { 60 | color:#aaa; 61 | } 62 | .leaflet-control-search .search-button { 63 | display:block; 64 | float:left; 65 | width:26px; 66 | height:26px; 67 | background: url('../images/search-icon.png') no-repeat 2px 2px; 68 | border-radius:4px; 69 | } 70 | .leaflet-control-search .search-button:hover { 71 | background: url('../images/search-icon.png') no-repeat 2px -22px; 72 | } 73 | .leaflet-control-search .search-tooltip { 74 | position:absolute; 75 | top:100%; 76 | left:0; 77 | float:left; 78 | min-width:120px; 79 | max-height:122px; 80 | box-shadow: 1px 1px 6px rgba(0,0,0,0.4); 81 | background-color: rgba(0, 0, 0, 0.25); 82 | z-index:1010; 83 | overflow-y:auto; 84 | overflow-x:hidden; 85 | } 86 | .leaflet-control-search .search-tip { 87 | margin:2px; 88 | padding:2px 4px; 89 | display:block; 90 | color:black; 91 | background: #eee; 92 | border-radius:.25em; 93 | text-decoration:none; 94 | white-space:nowrap; 95 | vertical-align:center; 96 | } 97 | .leaflet-control-search .search-tip-select, 98 | .leaflet-control-search .search-tip:hover, 99 | .leaflet-control-search .search-button:hover { 100 | background-color: #fff; 101 | } 102 | .leaflet-control-search .search-alert { 103 | cursor:pointer; 104 | clear:both; 105 | font-size:.75em; 106 | margin-bottom:5px; 107 | padding:0 .25em; 108 | color:#e00; 109 | font-weight:bold; 110 | border-radius:.25em; 111 | } 112 | 113 | 114 | -------------------------------------------------------------------------------- /client/vendor/styles/leaflet-search.mobile.css: -------------------------------------------------------------------------------- 1 | 2 | /* SEARCH */ 3 | .leaflet-control.leaflet-control-search { 4 | z-index:2000; 5 | } 6 | .leaflet-control-search .search-input { 7 | display:block; 8 | float:left; 9 | background: #fff; 10 | border:1px solid #666; 11 | border-radius:2px; 12 | height:24px; 13 | font-size:1.25em; 14 | padding:0 .125em; 15 | margin:3px; 16 | padding-right:30px; 17 | } 18 | .leaflet-control-search .search-button:hover, 19 | .leaflet-control-search .search-button { 20 | background-image: url('../images/search-icon-mobile.png'); 21 | border-radius: 4px; 22 | background-position: 1px 1px; 23 | width:32px; 24 | height:30px; 25 | } 26 | .leaflet-control-search.search-load .search-input { 27 | background: url('../images/loader.gif') no-repeat center right #fff; 28 | } 29 | .leaflet-control-search .search-cancel { 30 | background-image: url('../images/search-icon-mobile.png'); 31 | border-radius: 4px; 32 | background-position: 0px -62px; 33 | width:26px; 34 | height:26px; 35 | right:34px; 36 | margin:3px; 37 | } 38 | .leaflet-control-search .search-tooltip { 39 | max-height:142px;/*(.search-tip height * 5)*/ 40 | } 41 | .leaflet-control-search .search-tip { 42 | font-size:1em; 43 | margin:2px; 44 | padding:2px; 45 | display:block; 46 | color:black; 47 | background: rgba(255,255,255,0.8); 48 | border-radius:.25em; 49 | text-decoration:none; 50 | white-space:nowrap; 51 | vertical-align:center; 52 | } 53 | .leaflet-control-search .search-tip .climbo-icon-mini { 54 | float:right; 55 | display:block; 56 | white-space:nowrap; 57 | } 58 | .leaflet-control-search .search-button:hover, 59 | .leaflet-control-search .search-tip-select, 60 | .leaflet-control-search .search-tip:hover { 61 | background-color: #fff; 62 | } 63 | .leaflet-control-search .search-alert { 64 | font-size:1.2em; 65 | } 66 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "indentation" : { 3 | "value" : 4, 4 | "level" : "error" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cozy-photos", 3 | "version": "1.0.29", 4 | "author": "", 5 | "license": "AGPL-3.0", 6 | "description": "A cozy application to store, browse and share your photos.", 7 | "main": "build/server.js", 8 | "dependencies": { 9 | "americano": "0.4.5", 10 | "archiver": "1.0.0", 11 | "async": "1.5.2", 12 | "axon": "2.0.2", 13 | "cozy-clearance": "0.1.22", 14 | "cozy-realtime-adapter": "1.0.2", 15 | "cozydb": "0.1.10", 16 | "gm": "1.22.0", 17 | "jade": "1.11.0", 18 | "mime": "1.3.4", 19 | "multiparty": "4.1.2", 20 | "node-polyglot": "2.0.0", 21 | "printit": "0.1.18", 22 | "qs": "6.1.0", 23 | "socket.io": "1.4.5" 24 | }, 25 | "devDependencies": { 26 | "brunch": "1.8.5", 27 | "chai": "3.5.0", 28 | "request-json": "0.5.5", 29 | "mocha": "2.4.5", 30 | "sinon": "1.17.3", 31 | "coffee-script": "1.10.0" 32 | }, 33 | "scripts": { 34 | "test": "cake tests", 35 | "build:client": "cd client && brunch b", 36 | "build:server": "cake build", 37 | "build": "npm run build:client && npm run build:server", 38 | "start": "node build/server.js" 39 | }, 40 | "repository": "https://github.com/cozy/cozy-photos.git", 41 | "readmeFilename": "README.md", 42 | "cozy-permissions": { 43 | "Photo": { 44 | "description": "Creates and edits your photos" 45 | }, 46 | "Album": { 47 | "description": "Creates and edits your album which contains your photos." 48 | }, 49 | "Contact": { 50 | "description": "Allows you to easily share an album" 51 | }, 52 | "CozyInstance": { 53 | "description": "Read language setting" 54 | }, 55 | "File": { 56 | "description": "Import pictures from the Files app" 57 | }, 58 | "User": { 59 | "description": "Need information for the from part of email sent." 60 | }, 61 | "Send mail from user": { 62 | "description": "Share album with your friends" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /server.coffee: -------------------------------------------------------------------------------- 1 | americano = require 'americano' 2 | Photo = require './server/models/photo' 3 | 4 | process.on 'uncaughtException', (err) -> 5 | console.log err 6 | console.log err.stack 7 | 8 | setTimeout -> 9 | process.exit 1 10 | , 1000 11 | 12 | 13 | module.exports.start = start = (options, cb) -> 14 | options.name = 'cozy-photos' 15 | options.root ?= __dirname 16 | options.port ?= 9119 17 | options.host ?= '127.0.0.1' 18 | americano.start options, (err, app, server) -> 19 | return cb err if err 20 | 21 | Photo.patchGps (err) -> 22 | if err? 23 | console.log "Something went wrong during patch -- #{err}" 24 | else 25 | console.log "Patch successfully applied" 26 | module.exports.app = app 27 | cb?(null, app, server) 28 | 29 | if not module.parent 30 | start 31 | port: process.env.PORT 32 | host: process.env.HOST 33 | -------------------------------------------------------------------------------- /server/config.coffee: -------------------------------------------------------------------------------- 1 | americano = require 'americano' 2 | sharing = require './controllers/sharing' 3 | path = require 'path' 4 | fs = require 'fs' 5 | localizationManager = require('./helpers/localization_manager') 6 | RealtimeAdapter = require('cozy-realtime-adapter') 7 | init = require './helpers/initializer' 8 | thumb = require('./helpers/thumb').create 9 | File = require './models/file' 10 | path = require 'path' 11 | os = require 'os' 12 | 13 | staticMiddleware = americano.static __dirname + '/../client/public', 14 | maxAge: 86400000 15 | 16 | publicStatic = (req, res, next) -> 17 | url = req.url 18 | req.url = req.url.replace '/public', '' 19 | staticMiddleware req, res, (err) -> 20 | req.url = url 21 | next err 22 | 23 | useBuildView = fs.existsSync path.resolve(__dirname, 'views/index.js') 24 | 25 | module.exports = 26 | common: 27 | set: 28 | 'view engine': if useBuildView then 'js' else 'jade' 29 | 'views': path.resolve __dirname, 'views' 30 | 31 | engine: 32 | js: (path, locales, callback) -> 33 | callback null, require(path)(locales) 34 | 35 | use: [ 36 | americano.methodOverride() 37 | americano.bodyParser() 38 | 39 | staticMiddleware 40 | publicStatic 41 | 42 | sharing.markPublicRequests 43 | ] 44 | useAfter: [ 45 | americano.errorHandler 46 | dumpExceptions: true 47 | showStack: true 48 | ] 49 | afterStart: (app, server) -> 50 | 51 | app.server = server 52 | 53 | # pass reference to photo controller for socket.io upload progress 54 | require('./controllers/photo').setApp app 55 | 56 | # pass render engine to LocalizationManager 57 | viewEngine = app.render.bind app 58 | localizationManager.setRenderer viewEngine 59 | 60 | # create the uploads folder 61 | uploadFolder = path.join os.tmpdir(), 'uploads' 62 | try fs.mkdirSync uploadFolder 63 | catch err then if err.code isnt 'EEXIST' 64 | console.log "Something went wrong while creating uploads folder" 65 | console.log err 66 | # Ensure other apps can't read into this folder 67 | try fs.chmodSync uploadFolder, '0700' 68 | catch err 69 | console.log """ 70 | Something went wrong while setting rights on upload folder 71 | """ 72 | console.log err 73 | 74 | # Initialize realtime 75 | # contact, album & photo events are sent to client 76 | patterns = ['contact.*', 'album.*', 'photo.*'] 77 | realtime = RealtimeAdapter server, patterns 78 | 79 | 80 | development: [ 81 | americano.logger 'dev' 82 | ] 83 | production: [ 84 | americano.logger 'short' 85 | ] 86 | plugins: [ 87 | 'cozydb' 88 | ] 89 | -------------------------------------------------------------------------------- /server/controllers/contact.coffee: -------------------------------------------------------------------------------- 1 | Contact = require '../models/contact' 2 | async = require 'async' 3 | fs = require 'fs' 4 | 5 | module.exports.list = (req, res, next) -> 6 | Contact.all (err, contacts) -> 7 | if err 8 | next err 9 | else if not contacts 10 | err = new Error "Contacts not found" 11 | err.status = 404 12 | next err 13 | 14 | else 15 | res.send contacts 16 | -------------------------------------------------------------------------------- /server/controllers/routes.coffee: -------------------------------------------------------------------------------- 1 | photo = require './photo' 2 | album = require './album' 3 | file = require './file' 4 | contact = require './contact' 5 | sharing = require './sharing' 6 | 7 | 8 | module.exports = 9 | 10 | # fetch on params 11 | 'albumid': param: album.fetch 12 | 'photoid': param: photo.fetch 13 | 'fileid': param: file.fetch 14 | 15 | '': 16 | get: album.index 17 | 18 | 'albums/?': 19 | get: album.list 20 | post: album.create 21 | 22 | 'albums/:albumid.zip': 23 | get: album.zip 24 | 25 | 'albums/:albumid/?': 26 | get: album.read 27 | put: album.update 28 | delete: album.delete 29 | 30 | # 31 | 'files/': 32 | get: file.list 33 | 'files/:page': 34 | get: file.list 35 | 'files/thumbs/:fileid': 36 | get: file.thumb 37 | 'files/:fileid/toPhoto': 38 | post: file.createPhoto 39 | 40 | 41 | 'photos/?': 42 | post: photo.create 43 | 44 | 'photos/:photoid/?': 45 | put: photo.update 46 | delete: photo.delete 47 | 'photos/:photoid.jpg' : 48 | get : photo.screen 49 | 'photos/thumbs/:photoid.jpg': 50 | get : photo.thumb 51 | put : photo.updateThumb 52 | 'photos/raws/:photoid.jpg': 53 | get : photo.raw 54 | #MOFIF: rémi 55 | 'photos/': 56 | get : photo.fetchAll 57 | 58 | 'public/?' : get : album.index 59 | 'public/albums/?' : get : album.list 60 | 'public/albums/:albumid.zip' : get : album.zip 61 | 'public/albums/:albumid/?' : get : album.read 62 | #'public/photos/?' : post: photo.publicUpload 63 | 'public/photos/:photoid.jpg' : get : photo.screen 64 | 'public/photos/thumbs/:photoid.jpg' : get : photo.thumb 65 | 'public/photos/raws/:photoid.jpg' : get : photo.raw 66 | 67 | 68 | 'shareid': 69 | param: sharing.fetch 70 | 'clearance/contacts': 71 | get: sharing.contactList 72 | 'clearance/contacts/:contactid.jpg': 73 | get: sharing.contactPicture 74 | 'clearance/contacts/:contactid': 75 | get: sharing.contact 76 | 'clearance/:shareid': 77 | put: sharing.change 78 | 'clearance/:shareid/send': 79 | post: sharing.sendAll 80 | -------------------------------------------------------------------------------- /server/controllers/sharing.coffee: -------------------------------------------------------------------------------- 1 | async = require 'async' 2 | clearance = require 'cozy-clearance' 3 | cozydb = require 'cozydb' 4 | fs = require 'fs' 5 | 6 | Album = require '../models/album' 7 | 8 | localizationManager = require '../helpers/localization_manager' 9 | 10 | 11 | getUser = (callback) -> 12 | cozydb.api.getCozyUser (err, user) -> 13 | return callback err if err 14 | 15 | if user?.public_name and user.public_name.length > 0 16 | callback null, 17 | name: user.public_name 18 | email: user.email 19 | else 20 | localizationManager.ensureReady (err) -> 21 | callback null, 22 | name: localizationManager.t 'default user name' 23 | email: null 24 | 25 | 26 | clearanceCtl = clearance.controller 27 | mailTemplate: (options, callback) -> 28 | getUser (err, user) -> 29 | if err? 30 | callback err 31 | else 32 | options.displayName = user.name 33 | options.displayEmail= user.email 34 | localizationManager.render 'sharemail', options, callback 35 | 36 | mailSubject: (options, callback) -> 37 | getUser (err, user) -> 38 | if err? 39 | callback err 40 | else 41 | # Need to force locale to be loaded before executing callback 42 | localizationManager.ensureReady (err) -> 43 | callback null, localizationManager.t 'email sharing subject', 44 | displayName: user.name 45 | name: options.doc.title 46 | 47 | attachments: [ 48 | path: fs.realpathSync './build/client/public/img/cozy-logo.png' 49 | filename: 'cozy-logo.png' 50 | cid: 'cozy-logo' 51 | ] 52 | 53 | # fetch album, put it in req.doc 54 | module.exports.fetch = (req, res, next, id) -> 55 | Album.find id, (err, album) -> 56 | if album 57 | req.doc = album 58 | next() 59 | else 60 | err = new Error 'bad usage' 61 | err.status = 400 62 | next err 63 | 64 | # middleware to mark public request as such 65 | module.exports.markPublicRequests = (req, res, next) -> 66 | req.public = true if req.url.match /^\/public/ 67 | next() 68 | 69 | module.exports.checkPermissions = (album, req, callback) -> 70 | # owner can do everything 71 | return callback null, true unless req.public 72 | 73 | if album.clearance is 'hidden' 74 | album.clearance = 'public' 75 | 76 | if album.clearance is 'private' 77 | album.clearance = [] 78 | 79 | # public request are handled by cozy-clearance 80 | clearance.check album, 'r', req, callback 81 | 82 | # we cache album's clearance to avoid extra couchquery 83 | cache = {} 84 | module.exports.checkPermissionsPhoto = (photo, perm, req, callback) -> 85 | # owner can do everything 86 | return callback null, true unless req.public 87 | 88 | # public request are handled by cozy-clearance 89 | albumid = photo.albumid 90 | incache = cache[albumid] 91 | if incache 92 | clearance.check {clearance: incache}, perm, req, callback 93 | else 94 | Album.find albumid, (err, album) -> 95 | return callback null, false if err or not album 96 | if album.clearance is 'hidden' 97 | album.clearance = 'public' 98 | 99 | if album.clearance is 'private' 100 | album.clearance = [] 101 | cache[albumid] = album.clearance 102 | clearance.check album, perm, req, callback 103 | 104 | # overrige clearanceCtl to clear cache 105 | module.exports.change = (req, res, next) -> 106 | cache[req.params.shareid] = null 107 | clearanceCtl.change req, res, next 108 | 109 | module.exports.sendAll = clearanceCtl.sendAll 110 | module.exports.contactList = clearanceCtl.contactList 111 | module.exports.contact = clearanceCtl.contact 112 | module.exports.contactPicture = clearanceCtl.contactPicture 113 | -------------------------------------------------------------------------------- /server/helpers/downloader.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | 3 | # Module to handle file download with the low level http api instead of 4 | # request. This is due to a too high memory consumption while dowloading big 5 | # files with request. 6 | module.exports = 7 | 8 | # Returns the file in a callback as a readable stream of data. 9 | download: (path, callback) -> 10 | id = process.env.NAME 11 | pwd = process.env.TOKEN 12 | 13 | basic = "Basic #{new Buffer("#{id}:#{pwd}").toString('base64')}" 14 | options = 15 | host: 'localhost' 16 | port: 9101 17 | path: path 18 | headers: 19 | Authorization: basic 20 | 21 | http.get options, callback 22 | -------------------------------------------------------------------------------- /server/helpers/errors.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports.NotFound = (what) -> 3 | err = new Error what + ': Not Found' 4 | err.status = 404 5 | return err 6 | 7 | 8 | module.exports.NotAllowed = -> 9 | err = new Error 'Not allowed' 10 | err.status = 401 11 | return err 12 | 13 | module.exports.BadUsage = -> 14 | err = new Error 'Bad Usage' 15 | err.status = 400 16 | return err 17 | -------------------------------------------------------------------------------- /server/helpers/helpers.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | slugify: (text) -> 3 | text.replace(/[^-a-zA-Z0-9,&\s]+/ig, '') 4 | .replace(/-/gi, '_') 5 | .replace(/\s/gi, '-') 6 | 7 | noop: -> 8 | -------------------------------------------------------------------------------- /server/helpers/initializer.coffee: -------------------------------------------------------------------------------- 1 | Photo = require '../models/photo' 2 | File = require '../models/file' 3 | thumb = require('./thumb').create 4 | async = require 'async' 5 | 6 | 7 | convertImage = (cb) -> 8 | 9 | convert = (doc, callback) -> 10 | if doc._attachments? 11 | try 12 | console.log "Convert #{doc.title} ..." 13 | doc.convertBinary (err, res, body) -> 14 | console.log err if err? 15 | callback err 16 | catch error 17 | console.log "Cannot convert #{doc.title}" 18 | callback() 19 | else 20 | callback() 21 | 22 | Photo.all (err, docs) -> 23 | if err 24 | cb err 25 | else 26 | async.eachSeries docs, convert, cb 27 | 28 | # Create all requests and upload directory 29 | module.exports.convert = (socket, done= -> null) -> 30 | #convertImage (err) -> 31 | done() 32 | -------------------------------------------------------------------------------- /server/helpers/localization_manager.coffee: -------------------------------------------------------------------------------- 1 | Polyglot = require 'node-polyglot' 2 | cozydb = require 'cozydb' 3 | 4 | class LocalizationManager 5 | 6 | polyglot: null 7 | 8 | # should be run when app starts 9 | initialize: (callback = () ->) -> 10 | @ensureReady callback 11 | 12 | setRenderer: (renderer) -> 13 | @renderer = renderer 14 | 15 | retrieveLocale: (callback) -> 16 | cozydb.api.getCozyLocale (err, locale) -> 17 | if err? or not locale then locale = 'en' # default value 18 | callback null, locale 19 | 20 | ensureReady: (callback) -> 21 | return callback null, @polyglot if @polyglot 22 | # we are not ready, let's get ready 23 | @retrieveLocale (err, locale) => 24 | return callback err if err 25 | phrases = try require "../locales/#{locale}" 26 | catch err then require '../locales/en' 27 | 28 | @polyglot = new Polyglot locale: locale, phrases: phrases 29 | callback null, @polyglot 30 | 31 | # execute polyglot.t, for server-side localization 32 | t: (key, params = {}) -> 33 | return @polyglot?.t key, params 34 | 35 | render: (name, options, callback) -> 36 | @ensureReady (err) => 37 | return callback err if err 38 | viewName = "#{@polyglot.currentLocale}_#{name}" 39 | @renderer viewName, options, callback 40 | 41 | module.exports = new LocalizationManager() 42 | -------------------------------------------------------------------------------- /server/helpers/photo.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Orientation strings generated by GM lib when performing EXIF data extraction. 4 | codes = 5 | TopLeft: 1 6 | TopRight: 2 7 | BottomRight: 3 8 | BottomLeft: 4 9 | LeftTop: 5 10 | RightTop: 6 11 | RightBottom: 7 12 | LeftBottom: 8 13 | 14 | 15 | # Helpers to make photo metadata handling easy. 16 | module.exports = photoHelpers = 17 | 18 | 19 | # Turn given orientation in an integer code. 20 | getOrientation: (orientation) -> 21 | result = 1 22 | if typeof orientation is 'number' and 23 | orientation > 0 and 24 | orientation < 9 25 | result = orientation 26 | else if codes[orientation]? 27 | result = codes[orientation] 28 | result 29 | -------------------------------------------------------------------------------- /server/helpers/sharing.coffee: -------------------------------------------------------------------------------- 1 | Album = require '../models/album' 2 | clearance = require 'cozy-clearance' 3 | 4 | 5 | # check that doc is viewable by req 6 | # doc is visible iff any of itself or its parents is viewable 7 | module.exports.checkClearance = (doc, req, perm, callback) -> 8 | 9 | if typeof perm is "function" 10 | callback = perm 11 | perm = 'r' 12 | 13 | clearance.check doc, perm, req, (err, result) -> 14 | if result # the file itself is visible 15 | callback true 16 | else 17 | callback false 18 | -------------------------------------------------------------------------------- /server/img/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/server/img/error.gif -------------------------------------------------------------------------------- /server/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "default user name": "Someone", 3 | "email sharing subject": "%{displayName} share the photo album \"%{name}\" with you", 4 | "file": "Album", 5 | "link file content": "Cliquez ici pour voir ce fichier", 6 | "link folder content": "Cliquez ici pour naviguer dans ce dossier", 7 | "404 headline": "The album can't be found.", 8 | "404 option a": "You entered the wrong address", 9 | "404 option separator": "or", 10 | "404 option b": "the owner of this repository deleted that folder", 11 | "404 clippy sorry": "Désolé, je n'ai pas pu trouver l'album que vous cherchez", 12 | "404 clippy contact": "Vous pouvez toujours contacter le propriétaire de ce Cozy !" 13 | } -------------------------------------------------------------------------------- /server/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "default user name": "Someone", 3 | "email sharing subject": "%{displayName} share the photo album \"%{name}\" with you", 4 | "file": "album" 5 | } 6 | -------------------------------------------------------------------------------- /server/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "default user name": "Someone", 3 | "email sharing subject": "%{displayName} share the photo album \"%{name}\" with you", 4 | "file": "album", 5 | "link file content": "Cliquez ici pour voir ce fichier", 6 | "link folder content": "Cliquez ici pour naviguer dans ce dossier", 7 | "404 headline": "The album can't be found.", 8 | "404 option a": "You entered the wrong address", 9 | "404 option separator": "or", 10 | "404 option b": "the owner of this repository deleted that folder", 11 | "404 clippy sorry": "Désolé, je n'ai pas pu trouver l'album que vous cherchez", 12 | "404 clippy contact": "Vous pouvez toujours contacter le propriétaire de ce Cozy !" 13 | } -------------------------------------------------------------------------------- /server/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "default user name": "Quelqu'un", 3 | "email sharing subject": "%{displayName} partage l'album photo \"%{name}\" avec vous", 4 | "file": "album", 5 | "link file content": "Cliquez ici pour voir ce fichier", 6 | "link folder content": "Cliquez ici pour naviguer dans ce dossier", 7 | "404 headline": "Impossible de trouver l'album", 8 | "404 option a": "Vous n'avez pas saisi la bonne adresse", 9 | "404 option separator": "ou bien", 10 | "404 option b": "Le propriétaire de ce dépôt a supprimé ce répertoire", 11 | "404 clippy sorry": "Désolé, je n'ai pas pu trouver l'album que vous cherchez", 12 | "404 clippy contact": "Vous pouvez toujours contacter le propriétaire de ce Cozy !" 13 | } -------------------------------------------------------------------------------- /server/models/album.coffee: -------------------------------------------------------------------------------- 1 | cozydb = require 'cozydb' 2 | async = require 'async' 3 | Photo = require './photo' 4 | 5 | sanitize = (data) -> 6 | if data.title? 7 | data.title = data.title 8 | .replace /
/g, "" 9 | .replace /
/g, "" 10 | .replace /<\/div>/g, "" 11 | 12 | # Set default date if not set. 13 | data.date ?= new Date() 14 | 15 | module.exports = class Album extends cozydb.CozyModel 16 | @schema: 17 | id : String 18 | title : String 19 | description : String 20 | date : Date 21 | orientation : Number 22 | coverPicture : String 23 | clearance : cozydb.NoSchema 24 | folderid : String 25 | 26 | updateAttributes: (data) -> 27 | sanitize data 28 | super 29 | 30 | @create: (data) -> 31 | sanitize data 32 | super 33 | 34 | @listWithThumbs: (callback) -> 35 | async.parallel [ 36 | (cb) -> Album.request 'byTitle', cb 37 | (cb) -> Photo.albumsThumbs cb 38 | ], (err, results) -> 39 | return callback err if err 40 | [albums, defaultCovers] = results 41 | for album in albums 42 | defaultCover = defaultCovers[album.id] 43 | if defaultCover and not album.coverPicture 44 | [album.coverPicture, album.orientation] = defaultCover 45 | 46 | callback null, albums 47 | 48 | getPublicURL: (callback) -> 49 | cozydb.api.getCozyDomain (err, domain) => 50 | return callback err if err 51 | url = "#{domain}public/photos/#albums/#{@id}" 52 | callback null, url -------------------------------------------------------------------------------- /server/models/contact.coffee: -------------------------------------------------------------------------------- 1 | cozydb = require 'cozydb' 2 | 3 | # Contacts are required to make sharing easier. 4 | module.exports = class Contact extends cozydb.CozyModel 5 | id : String 6 | fn : String 7 | n : String 8 | datapoints : cozydb.NoSchema 9 | note : String 10 | _attachments : cozydb.NoSchema 11 | -------------------------------------------------------------------------------- /server/models/file.coffee: -------------------------------------------------------------------------------- 1 | cozydb = require 'cozydb' 2 | 3 | module.exports = class File extends cozydb.CozyModel 4 | @schema: 5 | id : String 6 | name : String 7 | path : String 8 | lastModification : String 9 | binary : cozydb.NoSchema 10 | class : String 11 | 12 | @imageByDate: (options, callback) -> 13 | File.request 'imageByDate', options, callback 14 | 15 | @withoutThumb: (callback) -> 16 | File.request 'withoutThumb', {}, callback 17 | -------------------------------------------------------------------------------- /server/models/photo.coffee: -------------------------------------------------------------------------------- 1 | cozydb = require 'cozydb' 2 | async = require 'async' 3 | fs = require 'fs' 4 | Helpers = require '../helpers/thumb' 5 | log = require('printit') 6 | date: true 7 | prefix: "model:photo" 8 | 9 | module.exports = class Photo extends cozydb.CozyModel 10 | @schema: 11 | id : String 12 | title : String 13 | description : String 14 | orientation : Number 15 | binary : cozydb.NoSchema 16 | _attachments : Object 17 | albumid : String 18 | date : String 19 | gps : Object 20 | 21 | 22 | # Get all photo linked to given album 23 | @fromAlbum: (album, callback) -> 24 | if album.folderid is "all" 25 | Photo.request 'all', {}, callback 26 | else 27 | params = 28 | startkey: [album.id] 29 | endkey: [album.id + "0"] 30 | Photo.request 'byalbum', params, callback 31 | 32 | # Patch every photo to add GPS data 33 | @patchGps: (callback) -> 34 | Photo.fromAlbum {folderId: "all" }, (err, photos) -> 35 | 36 | return callback err if err? # error on request fail 37 | async.eachSeries photos, (photo, next) -> 38 | 39 | # Don't try to extract data if we already got them 40 | if photo.binary? and not photo.gps? 41 | photo.extractGpsFromBinary next 42 | else setImmediate next 43 | , callback 44 | 45 | extractGpsFromBinary: (callback) -> 46 | kind = if @binary.raw then 'raw' else 'file' 47 | res = @getBinary kind, (err) -> 48 | log.error err if err? 49 | 50 | res.on 'ready', (stream) => 51 | Helpers.readMetadata stream, (err, data) => 52 | if err? 53 | log.error "Error reading metadata of #{@id} / #{@title}" 54 | log.error err 55 | callback() 56 | else 57 | @updateAttributes { gps: data.exif.gps }, (err) -> 58 | log.error err if err? 59 | callback() # ~ next() 60 | 61 | 62 | 63 | # Get all thumbnails of a given photo album. 64 | @albumsThumbs: (callback) -> 65 | params = 66 | reduce: true 67 | group: true 68 | 69 | Photo.rawRequest 'albumphotos', params, (err, results) -> 70 | return callback(err) if err 71 | 72 | out = {} 73 | for result in results 74 | out[result.key] = result.value 75 | 76 | callback null, out 77 | 78 | destroyWithBinary: (callback) -> 79 | if @binary? and typeof(@binary) is 'object' 80 | binaries = Object.keys @binary 81 | async.eachSeries binaries, (bin, cb) => 82 | @removeBinary bin, (err) => 83 | if err 84 | log.error """ 85 | Cannot destroy binary linked to photo #{@id}""" 86 | cb() 87 | , (err) => 88 | @destroy callback 89 | else 90 | @destroy callback 91 | -------------------------------------------------------------------------------- /server/models/requests.coffee: -------------------------------------------------------------------------------- 1 | # lint 2 | emit = null 3 | 4 | # MapReduce's map for "all" request 5 | allMap = (doc) -> emit doc._id, doc 6 | 7 | # Order docs by title 8 | byTitleMap = (doc) -> emit doc.title, doc 9 | 10 | # MapReduce's map to fetch photos by albumid 11 | byAlbumMap = (photo) -> emit [photo.albumid, photo.title], photo 12 | 13 | imageByDate = (doc) -> 14 | if doc.class is "image" and doc.binary?.file? 15 | emit doc.lastModification, doc 16 | 17 | withoutThumb = (doc) -> 18 | if doc.class is "image" and doc.binary?.file? and (not doc.binary.thumb?) 19 | emit doc._id, doc 20 | 21 | # MapReduce to fetch thumbs for every album 22 | albumPhotosRequest = 23 | map: (photo) -> emit photo.albumid, [photo._id, photo.orientation] 24 | reduce: (key, values, rereduce) -> values[0] 25 | 26 | module.exports = 27 | 'album': 28 | 'all': allMap 29 | 'byTitle': byTitleMap 30 | 'photo': 31 | 'all': allMap 32 | 'byalbum': byAlbumMap 33 | 'albumphotos': albumPhotosRequest 34 | 'contact': 35 | 'all': allMap 36 | 'file': 37 | 'imageByDate': imageByDate 38 | 'withoutThumb': withoutThumb 39 | -------------------------------------------------------------------------------- /server/views/en_sharemail.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html(style="height:100%;") 3 | head 4 | meta(name="viewport", content="width=device-width") 5 | meta(http-equiv="Content-Type", content="text/html; charset=UTF-8") 6 | 7 | body(style="height:100%; margin:0;") 8 | table(cellpadding="0" cellspacing="0" border="0", style="width: 100%; height: 100%; padding:0; margin:0; background-color: #F4F4F4; font-family: Helvetica,Arial,Verdana,sans-serif; color: #333; font-size: 0.9em; line-height: 1.6;") 9 | tr 10 | td(align="center", valign="top") 11 | table(cellpadding="0" cellspacing="0" border="0", style="width: 100%; max-width: 600px; padding: 2em 0.5em;") 12 | tr 13 | td 14 | table(cellpadding="0" cellspacing="0" border="0", style="width: 100%; max-width: 600px; background-color: #FFF; border-radius: 8px; border: 1px solid #DDD;") 15 | tr 16 | th(style="border-radius: 8px 8px 0 0; border-bottom: 1px solid #DDD;") 17 | img(src="cid:cozy-logo" width="63" height="48" style="padding: 12px 0; margin: auto;") 18 | tr 19 | // TODO: add user's email inside the brackets #{displayEmail} 20 | td(style="padding: 1.5em;") #{displayName} (#{displayEmail}) shared an album called "#{doc.title}" with you via Cozy Cloud. 21 | tr 22 | td(style="padding: 0.5em 1.5em 2em; width: 100%;") 23 | a(href=url, style="display: block; width: 60%; margin: 0 auto; padding: 1.2em 2em; background-color: #34A6FF; border-radius: 5px; color: #FFF; text-decoration: none; text-align: center; font-size: 1em; font-weight: bold; cursor:pointer;") 24 | | View album 25 | tr(style="padding-top: 10px; text-align: center; font-size: 0.85em; font-style: italic; color: #999;") 26 | td 27 | p 28 | | Sent from  29 | a(href="http://cozy.io", style="color: #34A6FF; text-decoration: none;") #{displayName}'s Cozy 30 | | . 31 | -------------------------------------------------------------------------------- /server/views/fr_sharemail.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html(style="height:100%;") 3 | head 4 | meta(name="viewport", content="width=device-width") 5 | meta(http-equiv="Content-Type", content="text/html; charset=UTF-8") 6 | 7 | body(style="height:100%; margin:0;") 8 | table(cellpadding="0" cellspacing="0" border="0", style="width: 100%; height: 100%; padding:0; margin:0; background-color: #F4F4F4; font-family: Helvetica,Arial,Verdana,sans-serif; color: #333; font-size: 0.9em; line-height: 1.6;") 9 | tr 10 | td(align="center", valign="top") 11 | table(cellpadding="0" cellspacing="0" border="0", style="width: 100%; max-width: 600px; padding: 2em 0.5em;") 12 | tr 13 | td 14 | table(cellpadding="0" cellspacing="0" border="0", style="width: 100%; max-width: 600px; background-color: #FFF; border-radius: 8px; border: 1px solid #DDD;") 15 | tr 16 | th(style="border-radius: 8px 8px 0 0; border-bottom: 1px solid #DDD;") 17 | img(src="cid:cozy-logo" width="63" height="48" style="padding: 12px 0; margin: auto;") 18 | tr 19 | // TODO: add user's email inside the brackets #{displayEmail} 20 | td(style="padding: 1.5em;") #{displayName} (#{displayEmail}) a partagé l'album "#{doc.title}" avec vous via Cozy Cloud. 21 | tr 22 | td(style="padding: 0.5em 1.5em 2em; width: 100%;") 23 | a(href=url, style="display: block; width: 60%; margin: 0 auto; padding: 1.2em 2em; background-color: #34A6FF; border-radius: 5px; color: #FFF; text-decoration: none; text-align: center; font-size: 1em; font-weight: bold; cursor:pointer;") 24 | | Voir Album 25 | tr(style="padding-top: 10px; text-align: center; font-size: 0.85em; font-style: italic; color: #999;") 26 | td 27 | p 28 | | Envoyé depuis le  29 | a(href="http://cozy.io", style="color: #34A6FF; text-decoration: none;") Cozy de #{displayName} 30 | | . 31 | -------------------------------------------------------------------------------- /server/views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset="utf-8") 5 | meta(http-equiv="X-UA-Compatible",content="IE=edge,chrome=1") 6 | meta(name="viewport", content="width=device-width,initial-scale=1") 7 | 8 | title Cozy - Photos 9 | 10 | link(rel="apple-touch-icon", sizes="57x57", href="/apps/photos/apple-touch-icon-57x57.png") 11 | link(rel="apple-touch-icon", sizes="60x60", href="/apps/photos/apple-touch-icon-60x60.png") 12 | link(rel="apple-touch-icon", sizes="72x72", href="/apps/photos/apple-touch-icon-72x72.png") 13 | link(rel="apple-touch-icon", sizes="76x76", href="/apps/photos/apple-touch-icon-76x76.png") 14 | link(rel="apple-touch-icon", sizes="114x114", href="/apps/photos/apple-touch-icon-114x114.png") 15 | link(rel="apple-touch-icon", sizes="120x120", href="/apps/photos/apple-touch-icon-120x120.png") 16 | link(rel="apple-touch-icon", sizes="144x144", href="/apps/photos/apple-touch-icon-144x144.png") 17 | link(rel="apple-touch-icon", sizes="152x152", href="/apps/photos/apple-touch-icon-152x152.png") 18 | link(rel="apple-touch-icon", sizes="180x180", href="/apps/photos/apple-touch-icon-180x180.png") 19 | link(rel="icon", type="image/png", href="/apps/photos/favicon-32x32.png", sizes="32x32") 20 | link(rel="icon", type="image/png", href="/apps/photos/favicon-194x194.png", sizes="194x194") 21 | link(rel="icon", type="image/png", href="/apps/photos/favicon-96x96.png", sizes="96x96") 22 | link(rel="icon", type="image/png", href="/apps/photos/android-chrome-192x192.png", sizes="192x192") 23 | link(rel="icon", type="image/png", href="/apps/photos/favicon-16x16.png", sizes="16x16") 24 | link(rel="manifest", href="/apps/photos/manifest.json") 25 | link(rel="shortcut icon", href="/apps/photos/favicon.ico") 26 | meta(name="msapplication-TileColor", content="#cdb19b") 27 | meta(name="msapplication-TileImage", content="/apps/photos/mstile-144x144.png") 28 | meta(name="msapplication-config", content="/apps/photos/browserconfig.xml") 29 | meta(name="theme-color", content="#cdb19b") 30 | 31 | link(rel="stylesheet", href="/fonts/fonts.css") 32 | link(rel="stylesheet", href="stylesheets/vendor.css") 33 | link(rel="stylesheet", href="stylesheets/app.css") 34 | 35 | body 36 | script!= imports 37 | script(src="javascripts/vendor.js") 38 | script(src="javascripts/app.js" onload="require('initialize');") 39 | -------------------------------------------------------------------------------- /tests/fixtures/album.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/tests/fixtures/album.zip -------------------------------------------------------------------------------- /tests/fixtures/data.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | baseAlbum: 4 | title: "Base" 5 | description: "Base Album" 6 | zip: "./tests/fixtures/album.zip" 7 | 8 | basePhoto1: 9 | title: "Photo 1" 10 | rawpath: "./tests/fixtures/test.jpg" 11 | screenpath: "./tests/fixtures/screen.jpg" 12 | thumbpath: "./tests/fixtures/thumb.jpg" 13 | 14 | basePhoto2: 15 | title: "Photo 2" 16 | rawpath: "./tests/fixtures/test.jpg" 17 | screenpath: "./tests/fixtures/screen.jpg" 18 | thumbpath: "./tests/fixtures/thumb.jpg" 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/tests/fixtures/screen.jpg -------------------------------------------------------------------------------- /tests/fixtures/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/tests/fixtures/test.jpg -------------------------------------------------------------------------------- /tests/fixtures/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cozy/cozy-photos-v2/c32d234ca5f33c41f63d3c87b5fd1d89a4e4a2e2/tests/fixtures/thumb.jpg -------------------------------------------------------------------------------- /tests/helpers.coffee: -------------------------------------------------------------------------------- 1 | TESTPORT = 8013 2 | Photo = Album = null 3 | Client = require('request-json').JsonClient 4 | intializeApp = require('../server').start 5 | 6 | module.exports = 7 | 8 | startServer: (done) -> 9 | @timeout 5000 10 | intializeApp port: TESTPORT, (err, app, server) => 11 | return done err if err 12 | app.server = server 13 | @app = app 14 | done() 15 | 16 | killServer: -> 17 | @app.server.close() 18 | 19 | clearDb: (done) -> 20 | @timeout 15000 21 | root = require('path').join __dirname, '..' 22 | require('cozydb').configure root, null, (err) -> 23 | return done err if err 24 | Photo = require '../server/models/photo' 25 | Album = require '../server/models/album' 26 | Photo.requestDestroy "all", (err) -> 27 | return done err if err 28 | Album.requestDestroy "all", done 29 | 30 | createAlbum: (data) -> (done) -> 31 | baseAlbum = new Album(data) 32 | Album.create baseAlbum, (err, album) => 33 | @album = album 34 | done err 35 | 36 | createPhoto: (data) -> (done) -> 37 | photo = new Photo(title:data.title, albumid:@album.id) 38 | Photo.create photo, (err, photo) -> 39 | return done err if err 40 | thumb = {name:'thumb', type: 'image/jpeg'} 41 | raw = {name:'raw', type: 'image/jpeg'} 42 | screen = {name:'raw', type: 'image/jpeg'} 43 | photo.attachBinary data.thumbpath, thumb, (err) -> 44 | return done err if err 45 | photo.attachBinary data.rawpath, raw, (err) -> 46 | return done err if err 47 | photo.attachBinary data.screenpath, screen, done 48 | 49 | 50 | makeTestClient: (done) -> 51 | old = new Client "http://localhost:#{TESTPORT}/" 52 | 53 | store = this # this will be the common scope of tests 54 | 55 | callbackFactory = (done) -> (error, response, body) -> 56 | throw error if(error) 57 | store.response = response 58 | store.body = body 59 | done() 60 | 61 | clean = -> 62 | store.response = null 63 | store.body = null 64 | 65 | store.client = 66 | get: (url, done) -> 67 | clean() 68 | old.get url, callbackFactory(done) 69 | post: (url, data, done) -> 70 | clean() 71 | old.post url, data, callbackFactory(done) 72 | put: (url, data, done) -> 73 | clean() 74 | old.put url, data, callbackFactory(done) 75 | del: (url, done) -> 76 | clean() 77 | old.del url, callbackFactory(done) 78 | sendFile: (url, path, done) -> 79 | old.sendFile url, path, callbackFactory(done) 80 | saveFile: (url, path, done) -> 81 | old.saveFile url, path, callbackFactory(done) 82 | 83 | # for fn in ['get', 'post', 'put', 'del', 'sendFile', 'saveFile'] 84 | # testClient[fn] = -> 85 | # clean() 86 | 87 | # args = Array.prototype.slice.call arguments, 0 88 | # args.push callbackFactory args.pop() 89 | 90 | # Client.prototype[fn].apply old, args 91 | 92 | # store.client = testClient 93 | 94 | done() 95 | -------------------------------------------------------------------------------- /tests/read.coffee: -------------------------------------------------------------------------------- 1 | fixtures = require './fixtures/data' 2 | fs = require 'fs' 3 | helpers = require './helpers' 4 | expect = require('chai').expect 5 | store = {} 6 | 7 | describe 'Read operations', -> 8 | 9 | before helpers.clearDb 10 | before 'create album', helpers.createAlbum fixtures.baseAlbum 11 | before 'create photo', helpers.createPhoto fixtures.basePhoto1 12 | before 'create photo', helpers.createPhoto fixtures.basePhoto2 13 | 14 | before 'start server', helpers.startServer 15 | before 'make client ', helpers.makeTestClient 16 | after helpers.killServer 17 | after -> 18 | fs.unlinkSync './test-get.jpg' 19 | fs.unlinkSync './test-get.zip' 20 | 21 | describe 'List - GET /albums', -> 22 | 23 | it 'should allow requests', (done) -> 24 | @client.get 'albums', done 25 | 26 | it 'should reply with the list of albums', -> 27 | expect(@body).to.be.an 'array' 28 | expect(@body).to.have.length 1 29 | expect(@body[0].id).to.exist 30 | expect(@body[0].title).to.equal fixtures.baseAlbum.title 31 | store.id = @body[0].id 32 | 33 | it 'should not include photos', -> 34 | expect(@body[0].photos).to.not.exist 35 | 36 | describe 'Read - GET /albums/:id', -> 37 | 38 | it 'should allow requests', (done) -> 39 | @client.get "albums/#{store.id}", done 40 | 41 | it 'should reply with one album', -> 42 | expect(@body.description).to.equal fixtures.baseAlbum.description 43 | expect(@body.title).to.equal fixtures.baseAlbum.title 44 | expect(@body.id).to.exist 45 | 46 | it 'should include photos', -> 47 | expect(@body.photos).to.be.an 'array' 48 | expect(@body.photos).to.have.length 2 49 | store.photoid = @body.photos[0].id 50 | 51 | describe 'Show photos - GET /photos/:id.jpg', -> 52 | 53 | downloadPath = './test-get.jpg' 54 | after: (done) -> fs.unlink downloadPath, done 55 | 56 | it "should allow requests", (done) -> 57 | @client.saveFile "photos/#{store.photoid}.jpg", downloadPath, done 58 | 59 | it "should not change the file", -> 60 | fileStats = fs.statSync(fixtures.basePhoto1.screenpath) 61 | resultStats = fs.statSync(downloadPath) 62 | expect(resultStats.size).to.equal fileStats.size 63 | 64 | describe 'Show thumb - GET /photos/tumbs/:id.jpg', -> 65 | 66 | downloadPath = './test-get.jpg' 67 | after: (done) -> fs.unlink downloadPath, done 68 | 69 | it "should allow requests", (done) -> 70 | url = "photos/thumbs/#{store.photoid}.jpg" 71 | @client.saveFile url, downloadPath, done 72 | 73 | it "should not change the file", -> 74 | fileStats = fs.statSync fixtures.basePhoto1.thumbpath 75 | resultStats = fs.statSync downloadPath 76 | expect(resultStats.size).to.equal fileStats.size 77 | 78 | describe 'ZIP - GET /albums/:id.zip', -> 79 | 80 | downloadPath = './test-get.zip' 81 | after: (done) -> fs.unlink downloadPath, done 82 | 83 | it "should allow requests", (done) -> 84 | url = "albums/#{store.id}.zip" 85 | @client.saveFile url, downloadPath , done 86 | 87 | it "should generate a zip", -> 88 | exist = fs.existsSync downloadPath 89 | expect(exist).to.equal true 90 | --------------------------------------------------------------------------------