Improved slideshow to support ?holiday URL matching

Added docker config

Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
James Ketrenos 2019-11-28 01:54:02 -08:00
parent 55a9c16e7b
commit c2f72df80a
11 changed files with 326 additions and 215 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
*
!entrypoint.sh

1
.env Normal file
View File

@ -0,0 +1 @@
USER_ID=1000:1000

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ node_modules
./elements ./elements
frontend/bower_components frontend/bower_components
pictures pictures
db
*.db

52
Dockerfile Normal file
View File

@ -0,0 +1,52 @@
FROM ubuntu:disco
RUN apt-get update
RUN apt-get install -y wget
# Upgrade Node
RUN wget -qO- https://deb.nodesource.com/setup_10.x | bash -
RUN DEBIAN_FRONTEND=NONINTERACTIVE apt-get install -y \
nodejs \
gcc \
g++ \
make \
cmake
# You can then install the latest npm, polymer-cli, and bower:
RUN npm install --global npm@latest npx
# Speed up face-recognition and dev tools
RUN apt-get install -y libopenblas-dev cmake
# NEF processing uses ufraw-batch
RUN apt-get install -y ufraw-batch
# Create a user with sudo access
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get install --no-install-recommends -y \
sudo
# NOTE: Requires 'sudo' package to already be installed
RUN groupadd -g 1000 user \
&& useradd --no-log-init \
-s /bin/bash \
-u 1000 \
-m \
-g user \
-G sudo \
-p $(echo "user" | openssl passwd -stdin) user
# Set 'sudo' to NOPASSWD for all container users
RUN sed -i -e 's,%sudo.*,%sudo ALL=(ALL) NOPASSWD:ALL,g' /etc/sudoers
COPY /entrypoint.sh /entrypoint.sh
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get install --no-install-recommends -y \
git
USER user
WORKDIR /website
CMD [ "/entrypoint.sh" ]

View File

@ -28,47 +28,6 @@ sudo apt install -y ufraw-batch
### Create `photos` user for DB ### Create `photos` user for DB
You will need to know the root password for your mariadb installation. If you Photos is currently using sqlite, which means you don't need to
need to reset the root password, you can perform the following: do anything fancy beyond having run 'npm install' to get sequelize
and sqlite3 installed.
```bash
export PASSWORD=m4g1cP4ssw0rd
sudo service mysql stop
sudo mysqld_safe --skip-grant-tables &
sleep 1 # mysqld_safe can take a bit of time to come online
sudo mysql << EOF
use mysql;
UPDATE user SET PASSWORD=PASSWORD("${PASSWORD}") WHERE USER='root';
UPDATE user SET plugin='mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
QUIT
EOF
sudo service mysql stop
sudo service mysql start
```
Once you know the root password, you can then create a new user and DB
for the photo app via:
### Create the USER
```bash
U='photos'
P='p4$$w0rd'
mysql -u root --password=${PASSWORD} << EOF
CREATE USER '${U}'@'localhost' IDENTIFIED BY '${P}';
EOF
```
### Create the DB 'photos'
```bash
U='photos'
P='p4$$w0rd'
D='photos'
mysql -u root --password=${PASSWORD} << EOF
DROP DATABASE IF EXISTS ${D};
CREATE DATABASE ${D} CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON ${D}.* TO '${U}'@'localhost' IDENTIFIED BY '${P}';
EOF
```

View File

@ -1,14 +1,14 @@
{ {
"db": { "db": {
"photos": { "photos": {
"host": "sqlite:photos.db", "host": "sqlite:db/photos.db",
"options": { "options": {
"logging" : false, "logging" : false,
"timezone": "+00:00" "timezone": "+00:00"
} }
}, },
"users": { "users": {
"host": "sqlite:users.db", "host": "sqlite:db/users.db",
"options": { "options": {
"logging" : false, "logging" : false,
"timezone": "+00:00" "timezone": "+00:00"

30
docker-compose.yml Normal file
View File

@ -0,0 +1,30 @@
version: '3.1'
services:
# db:
# image: mariadb
# restart: always
# environment:
# MYSQL_ROOT_PASSWORD: photos
# PHOTOS_DB_USER: photos
# PHOTOS_DB_PASSWD: ph0t0z
# PHOTOS_DB: photos
# volumes:
# - ${PWD}/db:/var/lib/mysql
# - ./init.sql:/data/application/init.sql
photos:
user: ${USER_ID}
build: .
image: photos:latest
container_name: photos
# depends_on:
# - db
restart: always
ports:
- 8123:8123
volumes:
- ${PWD}/photos:/photos
- ${PWD}/db:/db
- ${PWD}:/website

5
entrypoint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
while true; do
npm start
sleep 3
done

View File

@ -1,167 +1,225 @@
<html> <html>
<script src="src/ketr-photos/fetch.js"></script> <base href="/photos/">
<base href="/"> <body>
<body> <style>
<style> body {
body { margin: 0;
margin: 0; padding: 0;
padding: 0; }
}
#photo {
#photo { position: fixed;
position: fixed; display: inline-block;
display: inline-block; top: 0;
top: 0; right: 0;
right: 0; bottom: 0;
bottom: 0; left: 0;
left: 0; z-index: 0;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 50% 50%; background-position: 50% 50%;
background-size: contain; background-size: contain;
background-color: #222; background-color: #222;
} }
#placeholder { #placeholder {
position: absolute; position: absolute;
left: -1000px; left: -1000px;
display: none; display: none;
} }
#info { #footer {
position: absolute; position: absolute;
bottom: 0px; bottom: 0px;
left: 0px; left: 0px;
right: 0px; right: 0px;
padding: 0.5em 1em; opacity: 0.6;
background-color: #444; background: linear-gradient(45deg, rgba(16, 16, 16, 1), transparent);
color: white; color: white;
font-size: 1.5em; font-size: 1.5em;
line-height: 1.5em; line-height: 1.5em;
font-family: Arial; font-family: Arial;
box-shadow: 0px -0.25em 1em black; box-shadow: 0px -0.25em 1em black;
} text-shadow: 0 1px 0 black;
</style> z-index: 100;
<img id="placeholder"></img> display: flex;
<div id="photo"> flex-direction: row;
</div> justify-content: space-between;
<div id="info"> }
Loading photoset...
</div> #info {
<script> padding: 0.5em 1em;
var base, placeholder, info, photos = [], photoIndex; }
function shuffle(array) { #loading {
var index = array.length, tmp, random; padding: 0.5em 1em;
}
while (index) {
random = Math.floor(Math.random() * index); </style>
index--; <img id="placeholder"></img>
<div id="photo">
// And swap it with the current element. </div>
tmp = array[index]; <div id="footer">
array[index] = array[random]; <div id="info">Loading photoset...</div>
array[random] = tmp; <div id="loading"></div>
} </div>
<script>
return array; var base, placeholder, info, photos = [], photoIndex;
}
function shuffle(array) {
function loadPhoto(index) { var index = array.length, tmp, random;
var photo = photos[index];
placeholder.src = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename; while (index) {
} random = Math.floor(Math.random() * index);
index--;
function nextPhoto() {
photoIndex = (photoIndex + 1) % photos.length; // And swap it with the current element.
loadPhoto(photoIndex); tmp = array[index];
} array[index] = array[random];
array[random] = tmp;
var scheduled = false; }
function schedule() {
if (scheduled) { return array;
clearTimeout(scheduled); }
}
scheduled = setTimeout(function () { var countdown = 15;
scheduled = undefined;
nextPhoto(); const days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
}, 15 * 1000); months = [ "January", "February", "March", "April", "May", "June",
} "July", "August", "September", "October", "November", "December" ];
document.addEventListener("DOMContentLoaded", function() { function loadPhoto(index) {
var tmp = document.querySelector("base"); const photo = photos[index],
if (tmp) { xml = new XMLHttpRequest(),
base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */ url = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename,
} else { taken = new Date(photo.taken);
base = "/";
} document.getElementById("loading").textContent = "0%";
document.addEventListener("keydown", function(event) { xml.onprogress = function (event) {
if (event.keyCode == 37) { /* left */ var alpha = 0;
if (photoIndex == 0) { if (event.total) {
photoIndex = photos.length; alpha = event.loaded / event.total;
} document.getElementById("loading").textContent = Math.ceil(100 * alpha) + "%";
photoIndex--; } else {
loadPhoto(photoIndex); document.getElementById("loading").textContent = "0%";
return; }
} };
if (event.keyCode == 39) { /* right */ xml.onload = function(event) {
photoIndex = (photoIndex + 1) % photos.length; info.textContent =
loadPhoto(photoIndex); days[taken.getDay()] + ", " +
return; months[taken.getMonth()] + " " +
} taken.getDate() + " " +
taken.getFullYear();
console.log(event.keyCode); document.getElementById("photo").style.backgroundImage = "url(" + url + ")";
}); countdown = 15;
tick();
info = document.getElementById("info"); }
placeholder = document.getElementById("placeholder");
xml.onerror = function(event) {
placeholder.onload = function() { info.textContent = "Error loading photo. Trying next photo.";
var item = photos[photoIndex], nextPhoto();
days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], }
months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ], xml.open("GET", url, true);
taken = new Date(item.taken); xml.send();
}
info.textContent = "Photo taken on " +
days[taken.getDay()] + ", " + function nextPhoto() {
months[taken.getMonth()] + " " + photoIndex = (photoIndex + 1) % photos.length;
taken.getDate() + " " + loadPhoto(photoIndex);
taken.getFullYear(); }
photo.style.backgroundImage = "url(" + placeholder.src + ")"; var scheduled = false;
schedule(); function tick() {
}; if (scheduled) {
clearTimeout(scheduled);
placeholder.onerror = function() { }
info.textContent = "Error loading photo. Trying next photo."; document.getElementById("loading").textContent = countdown + "s";
nextPhoto(); if (countdown > 0) {
} /* If there is a timer running, then decrement the counter */
if (scheduled) {
window.fetch("api/v1/photos/holiday/memorial%20day", function(error, xhr) { countdown--;
if (error && xhr.status != 404) { }
console.log("Unable to fetch holiday: " + error); scheduled = setTimeout(tick, 1000);
return; } else {
} scheduled = null;
countdown = 15;
var results; nextPhoto();
try { }
results = JSON.parse(xhr.responseText); }
} catch (___) {
info.textContent = "Unable to parse results" function schedule() {
return; if (scheduled) {
} clearTimeout(scheduled);
}
if (results.items.length) { tick();
photos = shuffle(results.items); }
photoIndex = -1;
nextPhoto(); var paused = false;
} else { document.addEventListener("DOMContentLoaded", function() {
info.textContent = "No photos found."; var tmp = document.querySelector("base");
} if (tmp) {
}); base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
}); } else {
</script> base = "/";
</body> }
document.addEventListener("keydown", function(event) {
if (event.keyCode == 32) { /* space */
paused = !paused;
if (!paused) {
tick();
} else {
document.getElementById("loading").textContent = "||";
clearTimeout(scheduled);
scheduled = null;
}
return;
}
if (event.keyCode == 37) { /* left */
if (photoIndex == 0) {
photoIndex = photos.length;
}
photoIndex--;
loadPhoto(photoIndex);
return;
}
if (event.keyCode == 39) { /* right */
photoIndex = (photoIndex + 1) % photos.length;
loadPhoto(photoIndex);
return;
}
console.log(event.keyCode);
});
info = document.getElementById("info");
/* Trim off everything up to and including the ? (if its there) */
var parts = window.location.href.match(/^.*\?(.*)$/);
if (parts) {
holiday = parts[1];
} else {
holiday = "memorial day";
}
window.fetch("api/v1/photos/holiday/" + holiday.replace(/ +/g, "%20"))
.then(res => res.json()).then(function(data) {
if (data.items.length) {
info.textContent = photos.length + " photos found. Shuffling.";
photos = shuffle(data.items);
photoIndex = -1;
nextPhoto();
} else {
info.textContent = "No photos found for " + holiday + ".";
}
}).catch(function(error) {
console.error(error);
info.textContent = "Unable to fetch holiday :(";
});
});
</script>
</body>

View File

@ -12,6 +12,9 @@
}, },
"author": "James Ketrenos", "author": "James Ketrenos",
"license": "Apache-2.0", "license": "Apache-2.0",
"unused": {
"face-recognition": "^0.9.4"
},
"dependencies": { "dependencies": {
"bluebird": "^3.5.3", "bluebird": "^3.5.3",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
@ -21,7 +24,6 @@
"exif-reader": "github:paras20xx/exif-reader", "exif-reader": "github:paras20xx/exif-reader",
"express": "^4.16.4", "express": "^4.16.4",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"face-recognition": "^0.9.4",
"handlebars": "^4.0.12", "handlebars": "^4.0.12",
"ldapauth-fork": "^4.0.2", "ldapauth-fork": "^4.0.2",
"ldapjs": "^1.0.2", "ldapjs": "^1.0.2",

View File

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
sqlite3 photos.db sqlite3 db/photos.db