Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66a6d4003a | |||
| bdf73c1f06 | |||
| f032743690 | |||
| a9902b95b4 | |||
| 9d1c0b672a | |||
| 727fd8bae8 | |||
| 954103510a | |||
| 801e1f81d9 | |||
| f9861480aa |
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
mirror:
|
mirror:
|
||||||
@@ -19,11 +20,22 @@ jobs:
|
|||||||
git config --global user.name "the-luap"
|
git config --global user.name "the-luap"
|
||||||
git config --global user.email "paul-nothaft@hotmail.de"
|
git config --global user.email "paul-nothaft@hotmail.de"
|
||||||
|
|
||||||
|
- name: Debug - Show current branch and status
|
||||||
|
run: |
|
||||||
|
echo "Current branch:"
|
||||||
|
git branch -a
|
||||||
|
echo "Git status:"
|
||||||
|
git status
|
||||||
|
echo "Remote info:"
|
||||||
|
git remote -v
|
||||||
|
|
||||||
- name: Create filtered branch
|
- name: Create filtered branch
|
||||||
run: |
|
run: |
|
||||||
|
# Clean up any existing github-mirror branch
|
||||||
|
git branch -D github-mirror || true
|
||||||
|
|
||||||
# Create a new branch for GitHub
|
# Create a new branch for GitHub
|
||||||
git checkout --orphan -b github-mirror
|
git checkout --orphan github-mirror
|
||||||
|
|
||||||
# Remove sensitive files/directories
|
# Remove sensitive files/directories
|
||||||
# Example: Remove .env files, private configs, etc.
|
# Example: Remove .env files, private configs, etc.
|
||||||
@@ -43,17 +55,45 @@ jobs:
|
|||||||
git rm -r --cached CLAUDE.md || true
|
git rm -r --cached CLAUDE.md || true
|
||||||
git rm -r --cached PRODUCTION_DEPLOYMENT_GUIDE.md || true
|
git rm -r --cached PRODUCTION_DEPLOYMENT_GUIDE.md || true
|
||||||
git rm -r --cached logs/ || true
|
git rm -r --cached logs/ || true
|
||||||
|
git rm -r --cached frontend/.claudedocs/ || true
|
||||||
|
git rm -r --cached test-maintenance.sh || true
|
||||||
|
git rm -r --cached storage/ || true
|
||||||
|
|
||||||
|
|
||||||
# Commit the changes
|
# Commit the changes
|
||||||
git commit -m "Remove sensitive files for GitHub mirror" || true
|
git commit -m "Remove sensitive files for GitHub mirror" || true
|
||||||
|
|
||||||
|
- name: Check GitHub token
|
||||||
|
env:
|
||||||
|
GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$GITHUBTOKEN" ]; then
|
||||||
|
echo "ERROR: GITHUBTOKEN secret is not set!"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "GitHub token is available (length: ${#GITHUBTOKEN})"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Push to GitHub
|
- name: Push to GitHub
|
||||||
env:
|
env:
|
||||||
GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }}
|
GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
# Remove existing github remote if it exists
|
||||||
|
git remote remove github || true
|
||||||
|
|
||||||
# Add GitHub remote
|
# Add GitHub remote
|
||||||
git remote add github https://x-access-token:${GITHUBTOKEN}@github.com/the-luap/picpeak.git
|
git remote add github https://x-access-token:${GITHUBTOKEN}@github.com/the-luap/picpeak.git
|
||||||
|
|
||||||
|
# Verify remote was added
|
||||||
|
echo "GitHub remote added:"
|
||||||
|
git remote -v
|
||||||
|
|
||||||
# Force push the filtered branch to GitHub main
|
# Force push the filtered branch to GitHub main
|
||||||
git push github github-mirror:main --force
|
echo "Pushing to GitHub..."
|
||||||
|
git push github github-mirror:main --force
|
||||||
|
echo "Push completed successfully!"
|
||||||
|
|
||||||
|
- name: Workflow completed
|
||||||
|
run: |
|
||||||
|
echo "✅ Mirror to GitHub workflow completed successfully!"
|
||||||
|
echo "Check https://github.com/the-luap/picpeak to verify the mirror."
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.40",
|
"version": "1.0.44",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.40",
|
"version": "1.0.44",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.40",
|
"version": "1.0.44",
|
||||||
"description": "Backend for PicPeak event photo sharing platform",
|
"description": "Backend for PicPeak event photo sharing platform",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -78,6 +78,16 @@ function validatePassword(password, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip zxcvbn check if explicitly disabled (for gallery passwords)
|
||||||
|
if (options.skipStrengthCheck) {
|
||||||
|
return {
|
||||||
|
valid: errors.length === 0,
|
||||||
|
errors,
|
||||||
|
score: 2, // Default moderate score for gallery passwords
|
||||||
|
feedback: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Use zxcvbn for strength analysis
|
// Use zxcvbn for strength analysis
|
||||||
const strength = zxcvbn(password);
|
const strength = zxcvbn(password);
|
||||||
|
|
||||||
@@ -111,7 +121,52 @@ function validatePassword(password, options = {}) {
|
|||||||
* @returns {Object} - Validation result
|
* @returns {Object} - Validation result
|
||||||
*/
|
*/
|
||||||
function validatePasswordInContext(password, context, userData = {}) {
|
function validatePasswordInContext(password, context, userData = {}) {
|
||||||
// Base validation
|
// For gallery context, use more lenient validation
|
||||||
|
if (context === 'gallery') {
|
||||||
|
// Gallery-specific validation options
|
||||||
|
const galleryOptions = {
|
||||||
|
minLength: 6, // Reduced minimum length
|
||||||
|
requireUppercase: false, // Don't require uppercase for galleries
|
||||||
|
requireLowercase: false, // Don't require lowercase for galleries
|
||||||
|
requireNumbers: false, // Numbers are optional
|
||||||
|
requireSpecialChars: false, // Special chars are optional
|
||||||
|
preventCommonPasswords: true, // Still prevent common passwords
|
||||||
|
minStrengthScore: 0, // Accept any score for galleries
|
||||||
|
skipStrengthCheck: true // Skip zxcvbn strength analysis for galleries
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base validation with gallery-specific options
|
||||||
|
const result = validatePassword(password, galleryOptions);
|
||||||
|
|
||||||
|
// Override validation for common date formats
|
||||||
|
// Allow passwords like "04.07.2025", "04/07/2025", "04-07-2025"
|
||||||
|
const datePattern = /^\d{1,2}[.\/-]\d{1,2}[.\/-]\d{4}$/;
|
||||||
|
if (datePattern.test(password)) {
|
||||||
|
// Date format is valid for gallery passwords
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
errors: [],
|
||||||
|
score: 2,
|
||||||
|
feedback: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional gallery-specific checks
|
||||||
|
if (password.length < 6) {
|
||||||
|
result.valid = false;
|
||||||
|
result.errors = ['Password must be at least 6 characters long'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's too simple (e.g., just "123456")
|
||||||
|
if (/^\d{1,6}$/.test(password)) {
|
||||||
|
result.valid = false;
|
||||||
|
result.errors.push('Password cannot be just numbers. Consider using a date format like "04.07.2025"');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base validation for other contexts
|
||||||
const result = validatePassword(password);
|
const result = validatePassword(password);
|
||||||
|
|
||||||
// Context-specific validation
|
// Context-specific validation
|
||||||
@@ -136,16 +191,6 @@ function validatePasswordInContext(password, context, userData = {}) {
|
|||||||
result.errors.push('Password must not contain parts of your email');
|
result.errors.push('Password must not contain parts of your email');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (context === 'gallery') {
|
|
||||||
// Gallery passwords can be more lenient for user convenience
|
|
||||||
// Allow passwords with score >= 1 (weak but acceptable)
|
|
||||||
if (result.score < 1) {
|
|
||||||
result.valid = false;
|
|
||||||
result.errors.push('Password is too simple. Please add more complexity');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't check for event name in password - allow date-based passwords
|
|
||||||
// This allows passwords like "Sommer2025!" which users prefer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.40",
|
"version": "1.0.44",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.40",
|
"version": "1.0.44",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"@tiptap/extension-link": "^2.25.0",
|
"@tiptap/extension-link": "^2.25.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.40",
|
"version": "1.0.44",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
|||||||
{/* Close button */}
|
{/* Close button */}
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="absolute top-4 right-4 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-10"
|
className="absolute top-4 right-4 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-20"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<X className="w-6 h-6 text-white" />
|
<X className="w-6 h-6 text-white" />
|
||||||
@@ -171,7 +171,7 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
|||||||
{/* Navigation buttons */}
|
{/* Navigation buttons */}
|
||||||
<button
|
<button
|
||||||
onClick={goToPrevious}
|
onClick={goToPrevious}
|
||||||
className="absolute left-4 top-1/2 -translate-y-1/2 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-10"
|
className="absolute left-4 top-1/2 -translate-y-1/2 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-20"
|
||||||
aria-label="Previous photo"
|
aria-label="Previous photo"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-6 h-6 text-white" />
|
<ChevronLeft className="w-6 h-6 text-white" />
|
||||||
@@ -179,20 +179,19 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={goToNext}
|
onClick={goToNext}
|
||||||
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-10"
|
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-20"
|
||||||
aria-label="Next photo"
|
aria-label="Next photo"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-6 h-6 text-white" />
|
<ChevronRight className="w-6 h-6 text-white" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Bottom toolbar */}
|
{/* Bottom toolbar */}
|
||||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4">
|
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4 z-20">
|
||||||
<div className="max-w-4xl mx-auto flex items-center justify-between">
|
<div className="max-w-4xl mx-auto flex items-center justify-between">
|
||||||
<div className="text-white">
|
<div className="text-white">
|
||||||
<p className="text-sm opacity-75">
|
<p className="text-sm opacity-75">
|
||||||
{currentIndex + 1} / {photos.length}
|
{currentIndex + 1} / {photos.length}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-medium">{currentPhoto.filename}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -231,7 +230,7 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
|||||||
|
|
||||||
{/* Image container */}
|
{/* Image container */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 flex items-center justify-center"
|
className="absolute inset-0 flex items-center justify-center z-0"
|
||||||
onClick={handleImageClick}
|
onClick={handleImageClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
@@ -257,7 +256,7 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Touch/swipe indicators for mobile */}
|
{/* Touch/swipe indicators for mobile */}
|
||||||
<div className="absolute bottom-20 left-1/2 -translate-x-1/2 text-white text-sm opacity-50 pointer-events-none md:hidden">
|
<div className="absolute bottom-20 left-1/2 -translate-x-1/2 text-white text-sm opacity-50 pointer-events-none md:hidden z-20">
|
||||||
Swipe to navigate
|
Swipe to navigate
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ export const CreateEventPage: React.FC = () => {
|
|||||||
newErrors.password = t('validation.passwordRequired');
|
newErrors.password = t('validation.passwordRequired');
|
||||||
} else if (formData.password.length < 6) {
|
} else if (formData.password.length < 6) {
|
||||||
newErrors.password = t('validation.passwordMinLength');
|
newErrors.password = t('validation.passwordMinLength');
|
||||||
|
} else if (/^\d{1,6}$/.test(formData.password)) {
|
||||||
|
// Prevent simple numeric passwords like "123456"
|
||||||
|
newErrors.password = t('validation.passwordTooSimple', 'Password cannot be just numbers. Consider using a date format like "04.07.2025"');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.password !== formData.confirm_password) {
|
if (formData.password !== formData.confirm_password) {
|
||||||
@@ -412,6 +415,7 @@ export const CreateEventPage: React.FC = () => {
|
|||||||
onChange={handleInputChange('password')}
|
onChange={handleInputChange('password')}
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
placeholder={t('events.enterPassword')}
|
placeholder={t('events.enterPassword')}
|
||||||
|
helperText={t('events.passwordHelperText', 'You can use dates like "04.07.2025" or any text with 6+ characters')}
|
||||||
leftIcon={<Lock className="w-5 h-5 text-neutral-400" />}
|
leftIcon={<Lock className="w-5 h-5 text-neutral-400" />}
|
||||||
className="pr-10"
|
className="pr-10"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -180,6 +180,9 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
|||||||
newErrors.password = t('validation.passwordRequired');
|
newErrors.password = t('validation.passwordRequired');
|
||||||
} else if (formData.password.length < 6) {
|
} else if (formData.password.length < 6) {
|
||||||
newErrors.password = t('validation.passwordMinLength');
|
newErrors.password = t('validation.passwordMinLength');
|
||||||
|
} else if (/^\d{1,6}$/.test(formData.password)) {
|
||||||
|
// Prevent simple numeric passwords like "123456"
|
||||||
|
newErrors.password = t('validation.passwordTooSimple', 'Password cannot be just numbers. Consider using a date format like "04.07.2025"');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.password !== formData.confirm_password) {
|
if (formData.password !== formData.confirm_password) {
|
||||||
@@ -309,7 +312,6 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
|||||||
value={formData.event_date}
|
value={formData.event_date}
|
||||||
onChange={handleInputChange('event_date')}
|
onChange={handleInputChange('event_date')}
|
||||||
error={errors.event_date}
|
error={errors.event_date}
|
||||||
min={format(new Date(), 'yyyy-MM-dd')}
|
|
||||||
leftIcon={<Calendar className="w-5 h-5" />}
|
leftIcon={<Calendar className="w-5 h-5" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -454,6 +456,7 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
|||||||
value={formData.password}
|
value={formData.password}
|
||||||
onChange={handleInputChange('password')}
|
onChange={handleInputChange('password')}
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
|
helperText={t('events.passwordHelperText', 'You can use dates like "04.07.2025" or any text with 6+ characters')}
|
||||||
leftIcon={<Lock className="w-5 h-5" />}
|
leftIcon={<Lock className="w-5 h-5" />}
|
||||||
rightIcon={
|
rightIcon={
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user