-
Groups is fully removed: No homepage navigation, Account feature, tier setting, API, or client code. Legacy group tables, entitlements, messages, voice files, and config are purged automatically. New cache-busted client: cyberchat-app.js?v=20260609.3. Browser and SQLite migration tests passed with no console errors.
This commit is contained in:
+71
-16
@@ -56,6 +56,13 @@ function getConfig(bool $reload = false): array {
|
||||
if (!file_exists(CONFIG_FILE)) throw new RuntimeException('config.json not found');
|
||||
$config = json_decode((string)file_get_contents(CONFIG_FILE), true);
|
||||
if (!is_array($config)) throw new RuntimeException('config.json is invalid: ' . json_last_error_msg());
|
||||
if (array_key_exists('groups', $config)) {
|
||||
unset($config['groups']);
|
||||
$json = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
if ($json !== false && is_writable(CONFIG_FILE)) {
|
||||
file_put_contents(CONFIG_FILE, $json . PHP_EOL, LOCK_EX);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
@@ -165,6 +172,51 @@ function ensureColumns(PDO $db, string $table, array $additions): void {
|
||||
}
|
||||
}
|
||||
|
||||
function removeLegacyGroupSchema(PDO $db, bool $mainDatabase): void {
|
||||
$messageColumns = tableColumns($db, 'messages');
|
||||
if (isset($messageColumns['group_id'])) {
|
||||
$voiceFiles = $db->query("SELECT DISTINCT voice_file FROM messages
|
||||
WHERE group_id IS NOT NULL AND voice_file IS NOT NULL AND voice_file != ''")->fetchAll(PDO::FETCH_COLUMN);
|
||||
$db->exec('PRAGMA foreign_keys=OFF');
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db->exec("DROP TABLE IF EXISTS messages_without_groups");
|
||||
$db->exec("CREATE TABLE messages_without_groups (
|
||||
id INTEGER PRIMARY KEY" . ($mainDatabase ? ' AUTOINCREMENT' : '') . ",
|
||||
user_id INTEGER NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
color TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
message_type TEXT NOT NULL DEFAULT 'text',
|
||||
voice_file TEXT,
|
||||
voice_mime TEXT,
|
||||
voice_duration REAL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
||||
day_key TEXT NOT NULL" .
|
||||
($mainDatabase ? ", FOREIGN KEY(user_id) REFERENCES users(id)" : '') . "
|
||||
)");
|
||||
$db->exec("INSERT INTO messages_without_groups
|
||||
(id,user_id,username,color,message,message_type,voice_file,voice_mime,voice_duration,created_at,day_key)
|
||||
SELECT id,user_id,username,color,message,message_type,voice_file,voice_mime,voice_duration,created_at,day_key
|
||||
FROM messages WHERE group_id IS NULL");
|
||||
$db->exec("DROP TABLE messages");
|
||||
$db->exec("ALTER TABLE messages_without_groups RENAME TO messages");
|
||||
$db->exec("DROP TABLE IF EXISTS group_members");
|
||||
$db->exec("DROP TABLE IF EXISTS chat_groups");
|
||||
$db->commit();
|
||||
foreach ($voiceFiles as $voiceFile) deleteVoiceFile((string)$voiceFile);
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction()) $db->rollBack();
|
||||
throw $e;
|
||||
} finally {
|
||||
$db->exec('PRAGMA foreign_keys=ON');
|
||||
}
|
||||
} else {
|
||||
$db->exec("DROP TABLE IF EXISTS group_members");
|
||||
$db->exec("DROP TABLE IF EXISTS chat_groups");
|
||||
}
|
||||
}
|
||||
|
||||
function initDB(PDO $db): void {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -226,6 +278,7 @@ function initDB(PDO $db): void {
|
||||
'voice_mime' => 'TEXT',
|
||||
'voice_duration' => 'REAL',
|
||||
]);
|
||||
removeLegacyGroupSchema($db, true);
|
||||
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS plans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -368,11 +421,9 @@ function seedPlans(PDO $db): void {
|
||||
$insertFeature->execute([$planId, $key, json_encode($value)]);
|
||||
}
|
||||
}
|
||||
$db->exec("DELETE FROM plan_features WHERE feature_key IN ('groups_available','group_member_limit')");
|
||||
$db->exec("UPDATE plans SET description='Premium voice, identity, security, and 90-day personal archives.'
|
||||
WHERE slug='plus' AND description='Premium voice, identity, security, and groups for four.'");
|
||||
$db->exec("UPDATE plans SET description='Premium voice, security, and one year of personal archives.'
|
||||
WHERE slug='pro' AND description='Expanded groups and one year of personal archives.'");
|
||||
$db->exec("DELETE FROM plan_features WHERE feature_key LIKE 'group%'");
|
||||
$db->exec("UPDATE plans SET description='Configurable premium chat features.'
|
||||
WHERE lower(description) LIKE '%group%'");
|
||||
$freeId = (int)$db->query("SELECT id FROM plans WHERE slug = 'free'")->fetchColumn();
|
||||
$db->prepare("UPDATE users SET plan_id = ? WHERE plan_id IS NULL")->execute([$freeId]);
|
||||
}
|
||||
@@ -388,6 +439,7 @@ function plans(bool $activeOnly = false): array {
|
||||
foreach ($featureStmt->fetchAll() as $feature) {
|
||||
$row['features'][$feature['feature_key']] = json_decode($feature['value_json'], true);
|
||||
}
|
||||
$row['features'] = visiblePlanFeatures($row['features']);
|
||||
$row['id'] = (int)$row['id'];
|
||||
$row['price_cents'] = (int)$row['price_cents'];
|
||||
$row['active'] = (bool)$row['active'];
|
||||
@@ -395,6 +447,14 @@ function plans(bool $activeOnly = false): array {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function visiblePlanFeatures(array $features): array {
|
||||
return array_filter(
|
||||
$features,
|
||||
fn(string $key): bool => !str_starts_with(strtolower($key), 'group'),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
function planById(int $planId): ?array {
|
||||
foreach (plans(false) as $plan) if ($plan['id'] === $planId) return $plan;
|
||||
return null;
|
||||
@@ -425,7 +485,7 @@ function userPublicPayload(array $user): array {
|
||||
'email_verified' => !empty($user['email_verified_at']),
|
||||
'plan' => ['id' => $plan['id'], 'slug' => $plan['slug'], 'name' => $plan['name']],
|
||||
'plan_source' => !empty($user['manual_plan_id']) ? 'admin' : 'billing',
|
||||
'features' => $plan['features'],
|
||||
'features' => visiblePlanFeatures($plan['features']),
|
||||
'subscription' => [
|
||||
'status' => $user['subscription_status'] ?? 'free',
|
||||
'period_end' => isset($user['subscription_period_end']) ? (int)$user['subscription_period_end'] : null,
|
||||
@@ -506,11 +566,6 @@ function dayKey(): string {
|
||||
return date('Y-m-d');
|
||||
}
|
||||
|
||||
function publicMessagesOnly(PDO $db, string $alias = ''): string {
|
||||
$prefix = $alias !== '' ? rtrim($alias, '.') . '.' : '';
|
||||
return isset(tableColumns($db, 'messages')['group_id']) ? " AND {$prefix}group_id IS NULL" : '';
|
||||
}
|
||||
|
||||
function voiceUploadDir(): string {
|
||||
return storagePath((string)getConfigVal('voice.upload_dir', 'uploads/voice'));
|
||||
}
|
||||
@@ -544,6 +599,7 @@ function getArchiveDB(string $year, string $month, string $day): PDO {
|
||||
'message_type' => "TEXT NOT NULL DEFAULT 'text'",
|
||||
'voice_file' => 'TEXT', 'voice_mime' => 'TEXT', 'voice_duration' => 'REAL',
|
||||
]);
|
||||
removeLegacyGroupSchema($db, false);
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS archive_meta (key TEXT PRIMARY KEY,value TEXT)");
|
||||
$db->exec("CREATE INDEX IF NOT EXISTS idx_archive_user ON messages(user_id,created_at)");
|
||||
return $db;
|
||||
@@ -567,8 +623,7 @@ function archiveYesterdayIfNeeded(): void {
|
||||
$flag = ROOT_DIR . '/db/.archived_' . $yesterday;
|
||||
if (file_exists($flag)) return;
|
||||
$db = getDB();
|
||||
$publicOnly = publicMessagesOnly($db);
|
||||
$stmt = $db->prepare("SELECT * FROM messages WHERE day_key=?$publicOnly ORDER BY id");
|
||||
$stmt = $db->prepare("SELECT * FROM messages WHERE day_key=? ORDER BY id");
|
||||
$stmt->execute([$yesterday]);
|
||||
$rows = $stmt->fetchAll();
|
||||
if ($rows) {
|
||||
@@ -590,7 +645,7 @@ function archiveYesterdayIfNeeded(): void {
|
||||
$meta->execute(['message_count', (string)count($rows)]);
|
||||
$archive->commit();
|
||||
}
|
||||
$db->prepare("DELETE FROM messages WHERE day_key=?$publicOnly")->execute([$yesterday]);
|
||||
$db->prepare("DELETE FROM messages WHERE day_key=?")->execute([$yesterday]);
|
||||
file_put_contents($flag, date('c'), LOCK_EX);
|
||||
}
|
||||
|
||||
@@ -600,7 +655,7 @@ function personalArchiveRows(array $user, string $search = '', int $limit = 500)
|
||||
$includeVoice = (bool)featureValue($user, 'voice_archive', false);
|
||||
$rows = [];
|
||||
$db = getDB();
|
||||
$sql = "SELECT * FROM messages WHERE user_id=? AND created_at>=?" . publicMessagesOnly($db);
|
||||
$sql = "SELECT * FROM messages WHERE user_id=? AND created_at>=?";
|
||||
$params = [$user['id'], $cutoff];
|
||||
if (!$includeVoice) $sql .= " AND message_type='text'";
|
||||
if ($search !== '') { $sql .= " AND message LIKE ?"; $params[] = '%' . $search . '%'; }
|
||||
@@ -611,7 +666,7 @@ function personalArchiveRows(array $user, string $search = '', int $limit = 500)
|
||||
|
||||
foreach (archiveFilesSince($cutoff) as [, $year, $month, $day]) {
|
||||
$archive = getArchiveDB($year, $month, $day);
|
||||
$archiveSql = "SELECT * FROM messages WHERE user_id=? AND created_at>=?" . publicMessagesOnly($archive);
|
||||
$archiveSql = "SELECT * FROM messages WHERE user_id=? AND created_at>=?";
|
||||
$archiveParams = [$user['id'], $cutoff];
|
||||
if (!$includeVoice) $archiveSql .= " AND message_type='text'";
|
||||
if ($search !== '') { $archiveSql .= " AND message LIKE ?"; $archiveParams[] = '%' . $search . '%'; }
|
||||
|
||||
Reference in New Issue
Block a user