diff --git a/README.md b/README.md
index 22b8c0c..0cb80e0 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,18 @@
# 📸 PicPeak - Open Source Photo Sharing for Events
-[](https://opensource.org/licenses/MIT)
-[](https://www.docker.com/)
-[](https://nodejs.org/)
-[](https://reactjs.org/)
+
+

+
+ [](https://opensource.org/licenses/MIT)
+ [](https://www.docker.com/)
+ [](https://nodejs.org/)
+ [](https://reactjs.org/)
+
**PicPeak** is a powerful, self-hosted open-source alternative to commercial photo-sharing platforms like PicDrop.com and Scrapbook.de. Designed specifically for photographers and event organizers, PicPeak makes it simple to share beautiful, time-limited photo galleries with clients while maintaining full control over your data and branding.

+> 📸 *Gallery preview will be updated soon with latest interface*
## 🌟 Why Choose PicPeak?
@@ -124,17 +129,34 @@ Found a security issue? Please email security@example.com
## 📸 Screenshots
+### 🎛️ **Admin Dashboard**
+Get a complete overview of your photo galleries, analytics, and system status.
+
+
+
+### 📊 **Analytics & Insights**
+Track gallery performance, view statistics, and monitor user engagement.
+
+
+
+### 📁 **Event Management**
+Organize and manage your photo galleries with intuitive event management tools.
+
+
+
+### ✨ **Key Interface Highlights**
+
-View Gallery Examples
+👆 Click to see more interface details
-### Admin Dashboard
-
+#### What makes PicPeak's interface special:
-### Client Gallery View
-
-
-### Mobile Experience
-
+- **🎨 Clean Design**: Modern, photographer-friendly interface
+- **📱 Responsive**: Perfect on desktop, tablet, and mobile
+- **⚡ Fast Loading**: Optimized for quick photo browsing
+- **🔒 Secure Access**: Password-protected galleries with expiration
+- **📤 Easy Uploads**: Drag & drop functionality for effortless photo management
+- **🎯 Client-Focused**: Intuitive gallery experience for your clients
diff --git a/backend/src/middleware/auth-enhanced-v2.js b/backend/src/middleware/auth-enhanced-v2.js
index d0661d7..28176e0 100644
--- a/backend/src/middleware/auth-enhanced-v2.js
+++ b/backend/src/middleware/auth-enhanced-v2.js
@@ -1,5 +1,6 @@
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { isTokenRevoked } = require('../utils/tokenRevocation');
const logger = require('../utils/logger');
@@ -57,7 +58,7 @@ async function adminAuth(req, res, next) {
// Check if admin still exists and is active
const admin = await db('admin_users')
- .where({ id: decoded.id, is_active: true })
+ .where({ id: decoded.id, is_active: formatBoolean(true) })
.first();
if (!admin) {
diff --git a/backend/src/middleware/auth-enhanced.js b/backend/src/middleware/auth-enhanced.js
index acbbf3d..29c1000 100644
--- a/backend/src/middleware/auth-enhanced.js
+++ b/backend/src/middleware/auth-enhanced.js
@@ -1,5 +1,6 @@
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const logger = require('../utils/logger');
/**
@@ -50,7 +51,7 @@ async function adminAuth(req, res, next) {
// Check if admin still exists and is active
const admin = await db('admin_users')
- .where({ id: decoded.id, is_active: true })
+ .where({ id: decoded.id, is_active: formatBoolean(true) })
.first();
if (!admin) {
@@ -165,7 +166,7 @@ async function photoAuth(req, res, next) {
// Allow both admin and gallery tokens
if (decoded.type === 'admin') {
const admin = await db('admin_users')
- .where({ id: decoded.id, is_active: true })
+ .where({ id: decoded.id, is_active: formatBoolean(true) })
.first();
if (!admin) {
diff --git a/backend/src/middleware/auth.js b/backend/src/middleware/auth.js
index 1bc3912..d218573 100644
--- a/backend/src/middleware/auth.js
+++ b/backend/src/middleware/auth.js
@@ -1,5 +1,6 @@
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
async function adminAuth(req, res, next) {
try {
@@ -9,7 +10,7 @@ async function adminAuth(req, res, next) {
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
- const admin = await db('admin_users').where({ id: decoded.id, is_active: true }).first();
+ const admin = await db('admin_users').where({ id: decoded.id, is_active: formatBoolean(true) }).first();
if (!admin) {
return res.status(401).json({ error: 'Invalid token' });
diff --git a/backend/src/middleware/gallery.js b/backend/src/middleware/gallery.js
index b07be1d..99482cb 100644
--- a/backend/src/middleware/gallery.js
+++ b/backend/src/middleware/gallery.js
@@ -1,5 +1,6 @@
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
// Middleware to verify gallery access
async function verifyGalleryAccess(req, res, next) {
@@ -10,7 +11,7 @@ async function verifyGalleryAccess(req, res, next) {
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
- const event = await db('events').where({ id: decoded.eventId, is_active: true }).first();
+ const event = await db('events').where({ id: decoded.eventId, is_active: formatBoolean(true) }).first();
if (!event) {
return res.status(404).json({ error: 'Gallery not found or expired' });
diff --git a/backend/src/middleware/photoAuth.js b/backend/src/middleware/photoAuth.js
index b729432..e4fc2cd 100644
--- a/backend/src/middleware/photoAuth.js
+++ b/backend/src/middleware/photoAuth.js
@@ -1,6 +1,7 @@
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
async function photoAuth(req, res, next) {
try {
@@ -27,7 +28,7 @@ async function photoAuth(req, res, next) {
if (decoded.type === 'gallery') {
// For thumbnails, we accept any valid gallery token
if (!eventSlug) {
- const event = await db('events').where({ slug: decoded.eventSlug, is_active: true }).first();
+ const event = await db('events').where({ slug: decoded.eventSlug, is_active: formatBoolean(true) }).first();
if (event) {
req.event = event;
return next();
@@ -35,7 +36,7 @@ async function photoAuth(req, res, next) {
}
// For regular photos, check if token matches the event
else if (decoded.eventSlug === eventSlug) {
- const event = await db('events').where({ slug: eventSlug, is_active: true }).first();
+ const event = await db('events').where({ slug: eventSlug, is_active: formatBoolean(true) }).first();
if (event) {
req.event = event;
return next();
@@ -72,7 +73,7 @@ async function photoAuth(req, res, next) {
return res.status(401).json({ error: 'Authentication required for thumbnails' });
}
- const event = await db('events').where({ slug: eventSlug, is_active: true }).first();
+ const event = await db('events').where({ slug: eventSlug, is_active: formatBoolean(true) }).first();
if (!event) {
return res.status(404).json({ error: 'Gallery not found' });
}
diff --git a/backend/src/routes/adminArchives.js b/backend/src/routes/adminArchives.js
index bc5505a..a052d1c 100644
--- a/backend/src/routes/adminArchives.js
+++ b/backend/src/routes/adminArchives.js
@@ -2,6 +2,7 @@ const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { adminAuth } = require('../middleware/auth-enhanced-v2');
const archiver = require('archiver');
const AdmZip = require('adm-zip');
@@ -16,7 +17,7 @@ router.get('/', adminAuth, async (req, res) => {
// Get total count
const totalCount = await db('events')
- .where('is_archived', true)
+ .where('is_archived', formatBoolean(true))
.count('id as count')
.first();
@@ -28,7 +29,7 @@ router.get('/', adminAuth, async (req, res) => {
db.raw('SUM(photos.size_bytes) as total_size')
)
.leftJoin('photos', 'events.id', 'photos.event_id')
- .where('events.is_archived', true)
+ .where('events.is_archived', formatBoolean(true))
.groupBy('events.id')
.orderBy('events.archived_at', 'desc')
.limit(limit)
@@ -84,7 +85,7 @@ router.get('/:id', adminAuth, async (req, res) => {
try {
const archive = await db('events')
.where('id', req.params.id)
- .where('is_archived', true)
+ .where('is_archived', formatBoolean(true))
.first();
if (!archive) {
@@ -140,7 +141,7 @@ router.post('/:id/restore', adminAuth, async (req, res) => {
try {
const archive = await db('events')
.where('id', req.params.id)
- .where('is_archived', true)
+ .where('is_archived', formatBoolean(true))
.first();
if (!archive) {
@@ -303,7 +304,7 @@ router.get('/:id/download', adminAuth, async (req, res) => {
try {
const archive = await db('events')
.where('id', req.params.id)
- .where('is_archived', true)
+ .where('is_archived', formatBoolean(true))
.first();
if (!archive) {
@@ -352,7 +353,7 @@ router.delete('/:id', adminAuth, async (req, res) => {
try {
const archive = await db('events')
.where('id', req.params.id)
- .where('is_archived', true)
+ .where('is_archived', formatBoolean(true))
.first();
if (!archive) {
diff --git a/backend/src/routes/adminCategories.js b/backend/src/routes/adminCategories.js
index a2efd14..f3d9000 100644
--- a/backend/src/routes/adminCategories.js
+++ b/backend/src/routes/adminCategories.js
@@ -1,6 +1,7 @@
const express = require('express');
const { body, validationResult } = require('express-validator');
const { db, logActivity } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { adminAuth } = require('../middleware/auth-enhanced-v2');
const router = express.Router();
@@ -8,7 +9,7 @@ const router = express.Router();
router.get('/global', adminAuth, async (req, res) => {
try {
const categories = await db('photo_categories')
- .where('is_global', true)
+ .where('is_global', formatBoolean(true))
.orderBy('name', 'asc');
res.json(categories);
@@ -25,7 +26,7 @@ router.get('/event/:eventId', adminAuth, async (req, res) => {
const categories = await db('photo_categories')
.where(function() {
- this.where('is_global', true)
+ this.where('is_global', formatBoolean(true))
.orWhere('event_id', eventId);
})
.orderBy('is_global', 'desc')
@@ -65,7 +66,7 @@ router.post('/', adminAuth, [
.where('slug', categorySlug)
.where(function() {
if (is_global) {
- this.where('is_global', true);
+ this.where('is_global', formatBoolean(true));
} else {
this.where('event_id', event_id);
}
diff --git a/backend/src/routes/adminSettings.js b/backend/src/routes/adminSettings.js
index a579b74..010e929 100644
--- a/backend/src/routes/adminSettings.js
+++ b/backend/src/routes/adminSettings.js
@@ -4,6 +4,7 @@ const path = require('path');
const fs = require('fs').promises;
const { body, validationResult } = require('express-validator');
const { db, logActivity } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { adminAuth } = require('../middleware/auth');
const { clearMaintenanceCache } = require('../middleware/maintenance');
const router = express.Router();
@@ -513,7 +514,7 @@ router.get('/storage/info', adminAuth, async (req, res) => {
// Get archive storage
const archives = await db('events')
- .where('is_archived', true)
+ .where('is_archived', formatBoolean(true))
.whereNotNull('archive_path')
.select('archive_path');
diff --git a/backend/src/routes/auth-enhanced-v2.js b/backend/src/routes/auth-enhanced-v2.js
index 8a67188..2f8eba7 100644
--- a/backend/src/routes/auth-enhanced-v2.js
+++ b/backend/src/routes/auth-enhanced-v2.js
@@ -3,6 +3,7 @@ const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { verifyRecaptcha } = require('../services/recaptcha');
const {
trackFailedAttempt,
@@ -248,7 +249,7 @@ router.post('/gallery/verify', [
return res.status(400).json({ error: 'reCAPTCHA verification failed' });
}
- const event = await db('events').where({ slug, is_active: true, is_archived: false }).first();
+ const event = await db('events').where({ slug, is_active: formatBoolean(true), is_archived: formatBoolean(false) }).first();
if (!event) {
// Don't reveal if gallery exists
await trackFailedAttempt(`gallery:${slug}`, ipAddress, userAgent);
diff --git a/backend/src/routes/auth-enhanced.js b/backend/src/routes/auth-enhanced.js
index e5bf58b..dbc282d 100644
--- a/backend/src/routes/auth-enhanced.js
+++ b/backend/src/routes/auth-enhanced.js
@@ -3,6 +3,7 @@ const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { verifyRecaptcha } = require('../services/recaptcha');
const {
trackFailedAttempt,
@@ -167,7 +168,7 @@ router.post('/gallery/verify', [
return res.status(400).json({ error: 'reCAPTCHA verification failed' });
}
- const event = await db('events').where({ slug, is_active: true, is_archived: false }).first();
+ const event = await db('events').where({ slug, is_active: formatBoolean(true), is_archived: formatBoolean(false) }).first();
if (!event) {
// Don't reveal if gallery exists
await trackFailedAttempt(`gallery:${slug}`, ipAddress, userAgent);
diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js
index 3b42fdb..76f8321 100644
--- a/backend/src/routes/auth.js
+++ b/backend/src/routes/auth.js
@@ -3,6 +3,7 @@ const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { body, validationResult } = require('express-validator');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { verifyRecaptcha } = require('../services/recaptcha');
const router = express.Router();
@@ -76,7 +77,7 @@ router.post('/gallery/verify', [
return res.status(400).json({ error: 'reCAPTCHA verification failed' });
}
- const event = await db('events').where({ slug, is_active: true, is_archived: false }).first();
+ const event = await db('events').where({ slug, is_active: formatBoolean(true), is_archived: formatBoolean(false) }).first();
if (!event) {
return res.status(404).json({ error: 'Gallery not found or expired' });
}
diff --git a/backend/src/routes/events.js b/backend/src/routes/events.js
index 7901350..e8efd8c 100644
--- a/backend/src/routes/events.js
+++ b/backend/src/routes/events.js
@@ -3,6 +3,7 @@ const { body, validationResult } = require('express-validator');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { adminAuth } = require('../middleware/auth-enhanced-v2');
const fs = require('fs').promises;
const path = require('path');
@@ -113,9 +114,9 @@ router.get('/', adminAuth, async (req, res) => {
let query = db('events').select('*');
if (status === 'active') {
- query = query.where('is_active', true);
+ query = query.where('is_active', formatBoolean(true));
} else if (status === 'archived') {
- query = query.where('is_archived', true);
+ query = query.where('is_archived', formatBoolean(true));
}
const events = await query.orderBy('created_at', 'desc');
@@ -162,7 +163,7 @@ router.delete('/:id', adminAuth, async (req, res) => {
try {
const { id } = req.params;
- await db('events').where('id', id).update({ is_active: false });
+ await db('events').where('id', id).update({ is_active: formatBoolean(false) });
res.json({ success: true });
} catch (error) {
@@ -188,7 +189,7 @@ router.post('/:id/extend', adminAuth, [
await db('events').where('id', id).update({
expires_at: newExpiration,
- is_active: true // Reactivate if expired
+ is_active: formatBoolean(true) // Reactivate if expired
});
res.json({ expires_at: newExpiration });
diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js
index 45376a4..14a0e2e 100644
--- a/backend/src/routes/gallery.js
+++ b/backend/src/routes/gallery.js
@@ -1,6 +1,7 @@
const express = require('express');
const jwt = require('jsonwebtoken');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const archiver = require('archiver');
const path = require('path');
const router = express.Router();
@@ -19,7 +20,7 @@ async function verifyGalleryAccess(req, res, next) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const event = await db('events')
- .where({ id: decoded.eventId, is_active: true, is_archived: false })
+ .where({ id: decoded.eventId, is_active: formatBoolean(true), is_archived: formatBoolean(false) })
.first();
if (!event) {
@@ -40,7 +41,7 @@ router.get('/:slug/verify-token/:token', async (req, res) => {
const { slug, token } = req.params;
const event = await db('events')
- .where({ slug, is_active: true, is_archived: false })
+ .where({ slug, is_active: formatBoolean(true), is_archived: formatBoolean(false) })
.select('id', 'share_link')
.first();
@@ -121,7 +122,7 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
// Get all categories for this event
const categories = await db('photo_categories')
.where(function() {
- this.where('is_global', true)
+ this.where('is_global', formatBoolean(true))
.orWhere('event_id', req.event.id);
})
.orderBy('is_global', 'desc')
diff --git a/backend/src/routes/protectedImages.js b/backend/src/routes/protectedImages.js
index 146daee..0004d9a 100644
--- a/backend/src/routes/protectedImages.js
+++ b/backend/src/routes/protectedImages.js
@@ -1,6 +1,7 @@
const express = require('express');
const path = require('path');
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { verifyGalleryAccess } = require('../middleware/gallery');
const watermarkService = require('../services/watermarkService');
const { getStoragePath } = require('../config/storage');
@@ -141,7 +142,7 @@ router.get('/:slug/photo/:photoId/signed/:token', async (req, res) => {
// Get event
const event = await db('events')
.where({ slug })
- .where('is_active', true)
+ .where('is_active', formatBoolean(true))
.first();
if (!event) {
diff --git a/backend/src/services/expirationChecker.js b/backend/src/services/expirationChecker.js
index 27ffbe0..4501f67 100644
--- a/backend/src/services/expirationChecker.js
+++ b/backend/src/services/expirationChecker.js
@@ -75,7 +75,7 @@ async function queueExpirationWarning(event) {
async function handleExpiredEvent(event) {
try {
// Mark as inactive
- await db('events').where('id', event.id).update({ is_active: false });
+ await db('events').where('id', event.id).update({ is_active: formatBoolean(false) });
// Queue expiration emails
await queueEmail(event.id, event.host_email, 'gallery_expired', {
diff --git a/backend/src/services/fileWatcher.js b/backend/src/services/fileWatcher.js
index 847f8a4..1094ba2 100644
--- a/backend/src/services/fileWatcher.js
+++ b/backend/src/services/fileWatcher.js
@@ -2,6 +2,7 @@ const chokidar = require('chokidar');
const path = require('path');
const fs = require('fs').promises;
const { db } = require('../database/db');
+const { formatBoolean } = require('../utils/dbCompat');
const { generateThumbnail } = require('./imageProcessor');
const logger = require('../utils/logger');
@@ -51,7 +52,7 @@ async function processNewPhoto(filePath) {
if (!['.jpg', '.jpeg', '.png', '.webp'].includes(ext)) return;
// Find the event
- const event = await db('events').where({ slug: eventSlug, is_active: true }).first();
+ const event = await db('events').where({ slug: eventSlug, is_active: formatBoolean(true) }).first();
if (!event) return;
// Get file stats
diff --git a/backend/src/utils/authSecurity.js b/backend/src/utils/authSecurity.js
index 8e61f3f..57a2ab6 100644
--- a/backend/src/utils/authSecurity.js
+++ b/backend/src/utils/authSecurity.js
@@ -4,6 +4,7 @@
*/
const { db } = require('../database/db');
+const { formatBoolean } = require('./dbCompat');
const logger = require('./logger');
// Configuration constants
@@ -59,7 +60,7 @@ async function trackSuccessfulLogin(identifier, ipAddress, userAgent) {
const cutoffTime = new Date(Date.now() - ATTEMPT_WINDOW);
await db('login_attempts')
.where('identifier', identifier)
- .where('success', false)
+ .where('success', formatBoolean(false))
.where('attempt_time', '<', cutoffTime.toISOString())
.delete();
} catch (error) {
@@ -79,7 +80,7 @@ async function checkAccountLockout(identifier) {
// Get recent failed attempts
const failedAttempts = await db('login_attempts')
.where('identifier', identifier)
- .where('success', false)
+ .where('success', formatBoolean(false))
.where('attempt_time', '>=', recentWindow.toISOString())
.orderBy('attempt_time', 'desc')
.limit(MAX_LOGIN_ATTEMPTS);