mirror of
https://github.com/polaroi8d/cactoide.git
synced 2026-03-22 14:15:28 +00:00
Compare commits
7 Commits
fix/remove
...
fix/missin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1fa879f36 | ||
|
|
b723aac180 | ||
|
|
277ad3ff14 | ||
|
|
52d48e4839 | ||
|
|
5b7178bec1 | ||
|
|
7531af9d29 | ||
|
|
6155cc44da |
@@ -10,4 +10,14 @@ APP_VERSION=latest
|
|||||||
PORT=5173
|
PORT=5173
|
||||||
HOSTNAME=0.0.0.0
|
HOSTNAME=0.0.0.0
|
||||||
|
|
||||||
|
# Logger configuration
|
||||||
|
LOG_PRETTY=true
|
||||||
|
LOG_LEVEL="trace"
|
||||||
|
|
||||||
|
# If you don't want to use the default home page you can turn off
|
||||||
|
# in this case the /discovery page remain the home of your site
|
||||||
PUBLIC_LANDING_INFO=true
|
PUBLIC_LANDING_INFO=true
|
||||||
|
|
||||||
|
# Federation config
|
||||||
|
FEDERATION_INSTANCE=false
|
||||||
|
|
||||||
|
|||||||
26
Makefile
26
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: help build up down db-only logs db-clean prune i18n lint format migrate-up migrate-down
|
.PHONY: help build up down db-only db-seed logs db-clean prune i18n lint format migrate-up migrate-down
|
||||||
|
|
||||||
# Database connection variables
|
# Database connection variables
|
||||||
DB_HOST ?= localhost
|
DB_HOST ?= localhost
|
||||||
@@ -19,10 +19,11 @@ help:
|
|||||||
@echo " up - Start all services"
|
@echo " up - Start all services"
|
||||||
@echo " down - Stop all services"
|
@echo " down - Stop all services"
|
||||||
@echo " db-only - Start only the database"
|
@echo " db-only - Start only the database"
|
||||||
|
@echo " db-seed - Seed the database with sample data"
|
||||||
@echo " logs - Show logs from all services"
|
@echo " logs - Show logs from all services"
|
||||||
@echo " db-clean - Clean up all Docker resources"
|
@echo " db-clean - Clean up all Docker resources"
|
||||||
@echo " prune - Clean up everything (containers, images, volumes)"
|
@echo " prune - Clean up everything (containers, images, volumes)"
|
||||||
@echo " i18n - Validate translation files"
|
@echo " i18n - List missing keys in translation file (use FILE=path/to/file.json)"
|
||||||
@echo " lint - Lint the project"
|
@echo " lint - Lint the project"
|
||||||
@echo " format - Format the project"
|
@echo " format - Format the project"
|
||||||
@echo " migrate-up - Apply invite-only events migration"
|
@echo " migrate-up - Apply invite-only events migration"
|
||||||
@@ -73,6 +74,17 @@ db-only:
|
|||||||
@echo "Starting only the database..."
|
@echo "Starting only the database..."
|
||||||
docker compose up -d postgres
|
docker compose up -d postgres
|
||||||
|
|
||||||
|
# Seed the database with sample data
|
||||||
|
db-seed:
|
||||||
|
@echo "Seeding database with sample data..."
|
||||||
|
@if [ -f "database/seed.sql" ]; then \
|
||||||
|
psql "$(DB_URL)" -f database/seed.sql && \
|
||||||
|
echo "Database seeded successfully!"; \
|
||||||
|
else \
|
||||||
|
echo "Seed file not found: database/seed.sql"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Show logs from all services
|
# Show logs from all services
|
||||||
logs:
|
logs:
|
||||||
@echo "Showing logs from all services..."
|
@echo "Showing logs from all services..."
|
||||||
@@ -94,8 +106,10 @@ format:
|
|||||||
@echo "Formatting the project..."
|
@echo "Formatting the project..."
|
||||||
npm run format
|
npm run format
|
||||||
|
|
||||||
#TODO: not working yet
|
# List missing keys in a translation file
|
||||||
i18n:
|
i18n:
|
||||||
@echo "Validating translation files..."
|
@if [ -z "$(FILE)" ]; then \
|
||||||
@if [ -n "$(FILE)" ]; then \
|
echo "Error: FILE variable is required. Example: make i18n FILE=src/lib/i18n/it.json"; \
|
||||||
./scripts/i18n-check.sh $(FILE); \
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@./scripts/i18n-check.sh --missing-only $(FILE)
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -59,6 +59,17 @@ make db-only
|
|||||||
npm run dev -- --open
|
npm run dev -- --open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Build the image in local
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build \
|
||||||
|
--build-arg LOG_PRETTY=${LOG_PRETTY:-true} \
|
||||||
|
--build-arg LOG_LEVEL=${LOG_LEVEL:-trace} \
|
||||||
|
--build-arg PUBLIC_LANDING_INFO=${PUBLIC_LANDING_INFO:-true} \
|
||||||
|
--build-arg FEDERATION_INSTANCE=${FEDERATION_INSTANCE:-true} \
|
||||||
|
-t cactoide-example .
|
||||||
|
```
|
||||||
|
|
||||||
Your app will be available at `http://localhost:5173`. You can use the Makefile commands to run the application or the database, eg.: `make db-only`.
|
Your app will be available at `http://localhost:5173`. You can use the Makefile commands to run the application or the database, eg.: `make db-only`.
|
||||||
|
|
||||||
Use the `database/seed.sql` if you want to populate your database with dummy data.
|
Use the `database/seed.sql` if you want to populate your database with dummy data.
|
||||||
@@ -103,12 +114,12 @@ Your instance will automatically expose:
|
|||||||
To add your instance to the global federation list (so other instances can discover your events):
|
To add your instance to the global federation list (so other instances can discover your events):
|
||||||
|
|
||||||
1. Fork the [Cactoide repository](https://github.com/polaroi8d/cactoide)
|
1. Fork the [Cactoide repository](https://github.com/polaroi8d/cactoide)
|
||||||
2. Add your instance URL to the `instances` array in [`federation.config.js`](https://github.com/polaroi8d/cactoide/blob/main/federation.config.js):
|
2. Add your instance URL to the `instances` array in `federation.config.js`:
|
||||||
3. Open a pull request to the main repository
|
3. Open a pull request to the main repository
|
||||||
|
|
||||||
Once merged, your instance will appear in the federation network, and other instances will be able to discover and display your public events.
|
Once merged, your instance will appear in the federation network, and other instances will be able to discover and display your public events.
|
||||||
|
|
||||||
You can view all registered federated instances in the main repository: [`federation.config.js`](https://github.com/polaroi8d/cactoide/blob/main/federation.config.js) file.
|
You can view all registered federated instances in the main repository: `federation.config.js` file.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS events (
|
|||||||
type VARCHAR(20) NOT NULL CHECK (type IN ('limited','unlimited')),
|
type VARCHAR(20) NOT NULL CHECK (type IN ('limited','unlimited')),
|
||||||
attendee_limit INTEGER CHECK (attendee_limit > 0),
|
attendee_limit INTEGER CHECK (attendee_limit > 0),
|
||||||
user_id VARCHAR(100) NOT NULL,
|
user_id VARCHAR(100) NOT NULL,
|
||||||
visibility VARCHAR(20) NOT NULL DEFAULT 'public' CHECK (visibility IN ('public','private')),
|
visibility VARCHAR(20) NOT NULL DEFAULT 'public' CHECK (visibility IN ('public','private', 'invite-only')),
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
@@ -34,6 +34,15 @@ CREATE TABLE IF NOT EXISTS rsvps (
|
|||||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Invite tokens
|
||||||
|
CREATE TABLE IF NOT EXISTS invite_tokens (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
event_id VARCHAR(8) NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
||||||
|
token VARCHAR(32) NOT NULL UNIQUE,
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
-- =======================================
|
-- =======================================
|
||||||
-- Indexes
|
-- Indexes
|
||||||
-- =======================================
|
-- =======================================
|
||||||
@@ -42,5 +51,9 @@ CREATE INDEX IF NOT EXISTS idx_events_date ON events(date);
|
|||||||
CREATE INDEX IF NOT EXISTS idx_events_location_type ON events(location_type);
|
CREATE INDEX IF NOT EXISTS idx_events_location_type ON events(location_type);
|
||||||
CREATE INDEX IF NOT EXISTS idx_rsvps_event_id ON rsvps(event_id);
|
CREATE INDEX IF NOT EXISTS idx_rsvps_event_id ON rsvps(event_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_rsvps_user_id ON rsvps(user_id);
|
CREATE INDEX IF NOT EXISTS idx_rsvps_user_id ON rsvps(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invite_tokens_event_id ON invite_tokens(event_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invite_tokens_token ON invite_tokens(token);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invite_tokens_expires_at ON invite_tokens(expires_at);
|
||||||
|
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
-- Migration: Add invite-only events feature
|
|
||||||
-- Created: 2024-12-20
|
|
||||||
-- Description: Adds invite-only visibility option and invite tokens table
|
|
||||||
|
|
||||||
-- Add 'invite-only' to the visibility enum
|
|
||||||
ALTER TABLE events
|
|
||||||
DROP CONSTRAINT IF EXISTS events_visibility_check;
|
|
||||||
|
|
||||||
ALTER TABLE events
|
|
||||||
ADD CONSTRAINT events_visibility_check
|
|
||||||
CHECK (visibility IN ('public', 'private', 'invite-only'));
|
|
||||||
|
|
||||||
-- Create invite_tokens table
|
|
||||||
CREATE TABLE IF NOT EXISTS invite_tokens (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
event_id VARCHAR(8) NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
|
||||||
token VARCHAR(32) NOT NULL UNIQUE,
|
|
||||||
expires_at TIMESTAMPTZ NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create indexes for invite_tokens table
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_invite_tokens_event_id ON invite_tokens(event_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_invite_tokens_token ON invite_tokens(token);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_invite_tokens_expires_at ON invite_tokens(expires_at);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
-- Rollback Migration: Remove invite-only events feature
|
|
||||||
-- Created: 2024-12-20
|
|
||||||
-- Description: Removes invite-only visibility option and invite tokens table
|
|
||||||
|
|
||||||
-- Drop invite_tokens table and its indexes
|
|
||||||
DROP INDEX IF EXISTS idx_invite_tokens_expires_at;
|
|
||||||
DROP INDEX IF EXISTS idx_invite_tokens_token;
|
|
||||||
DROP INDEX IF EXISTS idx_invite_tokens_event_id;
|
|
||||||
DROP TABLE IF EXISTS invite_tokens;
|
|
||||||
|
|
||||||
-- Revert visibility enum to original values
|
|
||||||
ALTER TABLE events
|
|
||||||
DROP CONSTRAINT IF EXISTS events_visibility_check;
|
|
||||||
|
|
||||||
ALTER TABLE events
|
|
||||||
ADD CONSTRAINT events_visibility_check
|
|
||||||
CHECK (visibility IN ('public', 'private'));
|
|
||||||
@@ -37,6 +37,10 @@ services:
|
|||||||
DATABASE_URL: ${DATABASE_URL:-postgres://cactoide:cactoide_password@postgres:5432/cactoide_database}
|
DATABASE_URL: ${DATABASE_URL:-postgres://cactoide:cactoide_password@postgres:5432/cactoide_database}
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
HOSTNAME: ${HOSTNAME:-0.0.0.0}
|
HOSTNAME: ${HOSTNAME:-0.0.0.0}
|
||||||
|
LOG_PRETTY: ${LOG_PRETTY:-true}
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-trace}
|
||||||
|
PUBLIC_LANDING_INFO: ${PUBLIC_LANDING_INFO:-true}
|
||||||
|
FEDERATION_INSTANCE: ${FEDERATION_INSTANCE:-true}
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -1,191 +1,89 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
# Find keys present in messages.json but missing in a translation file.
|
||||||
|
|
||||||
# Translation validation script
|
set -euo pipefail
|
||||||
# Compares a translation file against the source messages.json to find missing keys
|
|
||||||
|
|
||||||
set -e
|
SOURCE_DEFAULT="src/lib/i18n/messages.json"
|
||||||
|
|
||||||
# Colors for output
|
usage() {
|
||||||
RED='\033[0;31m'
|
echo "Usage: $0 [--missing-only] <translation.json> [source_messages.json]"
|
||||||
GREEN='\033[0;32m'
|
echo "Compares <translation.json> against messages.json and prints missing keys."
|
||||||
YELLOW='\033[1;33m'
|
exit 1
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Default paths
|
|
||||||
SOURCE_FILE="src/lib/i18n/messages.json"
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
||||||
|
|
||||||
# Function to show usage
|
|
||||||
show_usage() {
|
|
||||||
echo "Usage: $0 [LANGUAGE_FILE]"
|
|
||||||
echo ""
|
|
||||||
echo "Validates a translation file against the source messages.json"
|
|
||||||
echo ""
|
|
||||||
echo "Arguments:"
|
|
||||||
echo " LANGUAGE_FILE Path to the translation file to validate (e.g., src/lib/i18n/it.json)"
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 src/lib/i18n/it.json"
|
|
||||||
echo " $0 src/lib/i18n/fr.json"
|
|
||||||
echo ""
|
|
||||||
echo "If no file is provided, it will check all .json files in src/lib/i18n/ except messages.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to get all keys from a JSON file recursively
|
|
||||||
get_keys() {
|
|
||||||
local file="$1"
|
|
||||||
local prefix="$2"
|
|
||||||
|
|
||||||
# Use jq to extract all keys recursively
|
|
||||||
jq -r 'paths(scalars) as $p | $p | join(".")' "$file" | while read -r key; do
|
|
||||||
if [ -n "$prefix" ]; then
|
|
||||||
echo "${prefix}.${key}"
|
|
||||||
else
|
|
||||||
echo "$key"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate a single translation file
|
|
||||||
validate_file() {
|
|
||||||
local translation_file="$1"
|
|
||||||
local source_file="$2"
|
|
||||||
|
|
||||||
echo -e "${YELLOW}Validating: $translation_file${NC}"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
|
|
||||||
# Check if files exist
|
|
||||||
if [ ! -f "$source_file" ]; then
|
|
||||||
echo -e "${RED}Error: Source file $source_file not found${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "$translation_file" ]; then
|
|
||||||
echo -e "${RED}Error: Translation file $translation_file not found${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get all keys from source file
|
|
||||||
local source_keys
|
|
||||||
source_keys=$(get_keys "$source_file")
|
|
||||||
|
|
||||||
# Get all keys from translation file
|
|
||||||
local translation_keys
|
|
||||||
translation_keys=$(get_keys "$translation_file")
|
|
||||||
|
|
||||||
# Find missing keys
|
|
||||||
local missing_keys
|
|
||||||
missing_keys=$(comm -23 <(echo "$source_keys" | sort) <(echo "$translation_keys" | sort))
|
|
||||||
|
|
||||||
# Find extra keys (in translation but not in source)
|
|
||||||
local extra_keys
|
|
||||||
extra_keys=$(comm -13 <(echo "$source_keys" | sort) <(echo "$translation_keys" | sort))
|
|
||||||
|
|
||||||
# Count missing and extra keys
|
|
||||||
local missing_count
|
|
||||||
if [ -z "$missing_keys" ]; then
|
|
||||||
missing_count=0
|
|
||||||
else
|
|
||||||
missing_count=$(echo "$missing_keys" | wc -l | tr -d ' ')
|
|
||||||
fi
|
|
||||||
|
|
||||||
local extra_count
|
|
||||||
if [ -z "$extra_keys" ]; then
|
|
||||||
extra_count=0
|
|
||||||
else
|
|
||||||
extra_count=$(echo "$extra_keys" | wc -l | tr -d ' ')
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Report results
|
|
||||||
if [ "$missing_count" -eq 0 ] && [ "$extra_count" -eq 0 ]; then
|
|
||||||
echo -e "${GREEN} Perfect! All keys match.${NC}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$missing_count" -gt 0 ]; then
|
|
||||||
echo -e "${RED} Missing $missing_count key(s) in translation:${NC}"
|
|
||||||
echo "$missing_keys" | while read -r key; do
|
|
||||||
echo -e " ${RED}• $key${NC}"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$extra_count" -gt 0 ]; then
|
|
||||||
echo -e "${YELLOW} Extra $extra_count key(s) in translation (not in source):${NC}"
|
|
||||||
echo "$extra_keys" | while read -r key; do
|
|
||||||
echo -e " ${YELLOW}• $key${NC}"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Return error code if there are missing keys
|
|
||||||
if [ "$missing_count" -gt 0 ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
main() {
|
|
||||||
local translation_file="$1"
|
|
||||||
local source_file="$PROJECT_ROOT/$SOURCE_FILE"
|
|
||||||
local exit_code=0
|
|
||||||
|
|
||||||
# Change to project root directory
|
|
||||||
cd "$PROJECT_ROOT"
|
|
||||||
|
|
||||||
# If no file specified, check all translation files
|
|
||||||
if [ -z "$translation_file" ]; then
|
|
||||||
echo -e "${YELLOW}No file specified. Checking all translation files...${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Find all .json files in i18n directory except messages.json
|
|
||||||
local files
|
|
||||||
files=$(find src/lib/i18n -name "*.json" -not -name "messages.json" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -z "$files" ]; then
|
|
||||||
echo -e "${YELLOW}No translation files found in src/lib/i18n/${NC}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate each file
|
|
||||||
echo "$files" | while read -r file; do
|
|
||||||
if [ -n "$file" ]; then
|
|
||||||
if ! validate_file "$file" "$source_file"; then
|
|
||||||
exit_code=1
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return $exit_code
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate the specified file
|
|
||||||
if ! validate_file "$translation_file" "$source_file"; then
|
|
||||||
exit_code=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return $exit_code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if jq is installed
|
# Check if jq is installed
|
||||||
if ! command -v jq &> /dev/null; then
|
command -v jq >/dev/null 2>&1 || {
|
||||||
echo -e "${RED}Error: jq is required but not installed.${NC}"
|
echo "Error: jq is required but not installed." >&2
|
||||||
echo "Please install jq:"
|
echo "Please install jq:" >&2
|
||||||
echo " macOS: brew install jq"
|
echo " macOS: brew install jq" >&2
|
||||||
echo " Ubuntu/Debian: sudo apt-get install jq"
|
echo " Ubuntu/Debian: sudo apt-get install jq" >&2
|
||||||
echo " CentOS/RHEL: sudo yum install jq"
|
echo " CentOS/RHEL: sudo yum install jq" >&2
|
||||||
exit 1
|
exit 127
|
||||||
fi
|
}
|
||||||
|
|
||||||
# Handle help flag
|
# Parse arguments (handle --missing-only flag for Makefile compatibility)
|
||||||
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
TRANSLATION=""
|
||||||
show_usage
|
SOURCE="$SOURCE_DEFAULT"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--missing-only|-m)
|
||||||
|
# Flag is accepted but ignored (this script only does missing keys)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Error: Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$TRANSLATION" ]]; then
|
||||||
|
TRANSLATION="$1"
|
||||||
|
elif [[ "$SOURCE" == "$SOURCE_DEFAULT" ]]; then
|
||||||
|
SOURCE="$1"
|
||||||
|
else
|
||||||
|
echo "Error: Too many arguments" >&2
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate arguments
|
||||||
|
[[ -z "$TRANSLATION" ]] && {
|
||||||
|
echo "Error: Translation file is required" >&2
|
||||||
|
usage
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate files exist
|
||||||
|
[[ -f "$SOURCE" ]] || {
|
||||||
|
echo "Error: Source file not found: $SOURCE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -f "$TRANSLATION" ]] || {
|
||||||
|
echo "Error: Translation file not found: $TRANSLATION" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract all keys from a JSON file (dot-joined paths to scalar values)
|
||||||
|
keys() {
|
||||||
|
jq -r 'paths(scalars) | join(".")' "$1" | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find missing keys: in SOURCE but not in TRANSLATION
|
||||||
|
missing=$(comm -23 <(keys "$SOURCE") <(keys "$TRANSLATION"))
|
||||||
|
|
||||||
|
if [[ -z "$missing" ]]; then
|
||||||
|
echo "No missing keys found."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run main function
|
echo "Missing keys in $(basename "$TRANSLATION"):"
|
||||||
main "$1"
|
echo "$missing" | sed 's/^/ - /'
|
||||||
|
echo
|
||||||
|
echo "Total missing keys: $(echo "$missing" | wc -l | tr -d ' ')"
|
||||||
|
exit 1
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const config = {
|
const config = {
|
||||||
name: 'Cactoide Genesis',
|
name: 'Cactoide Genesis',
|
||||||
instances: [
|
instances: [
|
||||||
{
|
// {
|
||||||
url: 'cactoide.org'
|
// url: 'cactoide.org'
|
||||||
}
|
// }
|
||||||
// {
|
// {
|
||||||
// url: 'YOUR_INSTANCE_URL'
|
// url: 'YOUR_INSTANCE_URL'
|
||||||
// }
|
// }
|
||||||
@@ -1,76 +1,18 @@
|
|||||||
import { readFileSync } from 'fs';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { logger } from '$lib/logger';
|
import { logger } from '$lib/logger';
|
||||||
import type { Event } from '$lib/types';
|
import type { Event } from '$lib/types';
|
||||||
|
|
||||||
import config from '../../federation.config.js';
|
import config from '$lib/config/federation.config.js';
|
||||||
|
|
||||||
console.log(config.instances);
|
|
||||||
|
|
||||||
interface FederationConfig {
|
|
||||||
name: string;
|
|
||||||
instances: Array<{ url: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FederationEventsResponse {
|
interface FederationEventsResponse {
|
||||||
events: Array<Event & { federation?: boolean }>;
|
events: Array<Event & { federation?: boolean }>;
|
||||||
count?: number;
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the federation config file
|
|
||||||
*/
|
|
||||||
async function readFederationConfig(): Promise<FederationConfig | null> {
|
|
||||||
try {
|
|
||||||
const configPath = join(process.cwd(), 'federation.config.js');
|
|
||||||
|
|
||||||
// Use dynamic import to load the config file as a module
|
|
||||||
// This is safer than eval and works with ES modules
|
|
||||||
const configModule = await import(configPath + '?t=' + Date.now());
|
|
||||||
const config = (configModule.default || configModule.config) as FederationConfig;
|
|
||||||
|
|
||||||
if (config && config.instances && Array.isArray(config.instances)) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn('Invalid federation config structure');
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
// If dynamic import fails, try reading as text and parsing
|
|
||||||
try {
|
|
||||||
const configPath = join(process.cwd(), 'federation.config.js');
|
|
||||||
const configContent = readFileSync(configPath, 'utf-8');
|
|
||||||
|
|
||||||
// Try to extract JSON-like structure
|
|
||||||
const configMatch = configContent.match(/instances:\s*\[([\s\S]*?)\]/);
|
|
||||||
if (configMatch) {
|
|
||||||
// Simple parsing - extract URLs
|
|
||||||
const urlMatches = configContent.matchAll(/url:\s*['"]([^'"]+)['"]/g);
|
|
||||||
const instances = Array.from(urlMatches, (match) => ({ url: match[1] }));
|
|
||||||
|
|
||||||
if (instances.length > 0) {
|
|
||||||
return {
|
|
||||||
name: 'Federated Instances',
|
|
||||||
instances
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (fallbackError) {
|
|
||||||
logger.error({ error: fallbackError }, 'Error parsing federation.config.js as fallback');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.error({ error }, 'Error reading federation.config.js');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches events from a single federated instance
|
* Fetches events from a single federated instance
|
||||||
*/
|
*/
|
||||||
async function fetchEventsFromInstance(instanceUrl: string): Promise<Event[]> {
|
async function fetchEventsFromInstance(instanceUrl: string): Promise<Event[]> {
|
||||||
try {
|
try {
|
||||||
// Ensure URL has protocol and append /api/federation/events
|
|
||||||
|
|
||||||
const apiUrl = `http://${instanceUrl}/api/federation/events`;
|
const apiUrl = `http://${instanceUrl}/api/federation/events`;
|
||||||
|
|
||||||
logger.debug({ apiUrl }, 'Fetching events from federated instance');
|
logger.debug({ apiUrl }, 'Fetching events from federated instance');
|
||||||
@@ -120,18 +62,11 @@ async function fetchEventsFromInstance(instanceUrl: string): Promise<Event[]> {
|
|||||||
* Fetches events from all configured federated instances
|
* Fetches events from all configured federated instances
|
||||||
*/
|
*/
|
||||||
export async function fetchAllFederatedEvents(): Promise<Event[]> {
|
export async function fetchAllFederatedEvents(): Promise<Event[]> {
|
||||||
const config = await readFederationConfig();
|
|
||||||
|
|
||||||
if (!config || !config.instances || config.instances.length === 0) {
|
if (!config || !config.instances || config.instances.length === 0) {
|
||||||
logger.debug('No federation config or instances found');
|
logger.debug('No federation config or instances found');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
|
||||||
{ instanceCount: config.instances.length },
|
|
||||||
'Fetching events from federated instances'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch from all instances in parallel
|
// Fetch from all instances in parallel
|
||||||
const fetchPromises = config.instances.map((instance) => fetchEventsFromInstance(instance.url));
|
const fetchPromises = config.instances.map((instance) => fetchEventsFromInstance(instance.url));
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { database } from '$lib/database/db';
|
|||||||
import { events } from '$lib/database/schema';
|
import { events } from '$lib/database/schema';
|
||||||
import { eq, count } from 'drizzle-orm';
|
import { eq, count } from 'drizzle-orm';
|
||||||
import { logger } from '$lib/logger';
|
import { logger } from '$lib/logger';
|
||||||
import federationConfig from '../../../../../federation.config.js';
|
import federationConfig from '$lib/config/federation.config.js';
|
||||||
|
|
||||||
import { FEDERATION_INSTANCE } from '$env/static/private';
|
import { FEDERATION_INSTANCE } from '$env/static/private';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { logger } from '$lib/logger';
|
import { logger } from '$lib/logger';
|
||||||
import federationConfig from '../../../federation.config.js';
|
import federationConfig from '$lib/config/federation.config.js';
|
||||||
|
|
||||||
interface InstanceInfo {
|
interface InstanceInfo {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -130,10 +130,7 @@
|
|||||||
|
|
||||||
<p class="py-8 text-center text-slate-400">
|
<p class="py-8 text-center text-slate-400">
|
||||||
{t('instance.description')}
|
{t('instance.description')}
|
||||||
<a
|
{t('instance.configFile')}
|
||||||
href="https://github.com/cactoide/cactoide/blob/main/federation.config.js"
|
|
||||||
class="text-violet-300/80">{t('instance.configFile')}</a
|
|
||||||
>
|
|
||||||
{t('instance.file')}
|
{t('instance.file')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user