From 462502cf9fb8af56c42d15661153752cc1c5aa7f Mon Sep 17 00:00:00 2001 From: Franzz Date: Mon, 10 Aug 2026 16:41:20 +0200 Subject: [PATCH] Split authentication & newsletter subscription --- lib/Controller.php | 15 +- lib/Spot.php | 53 ++- lib/User.php | 272 +++++++++---- package-lock.json | 390 +++++++------------ resources/lang/en.json | 41 +- resources/lang/es.json | 41 +- resources/lang/fr.json | 41 +- resources/sql/20260808_account_password.sql | 7 + src/components/admin.vue | 10 +- src/components/projectAccount.vue | 113 ++++++ src/components/projectNewsletter.vue | 66 ---- src/components/projectSettings.vue | 10 +- src/scripts/api.js | 5 +- src/scripts/icons.js | 10 +- src/scripts/lang.js | 10 +- src/styles/_common.scss | 5 +- src/styles/_page.admin.scss | 4 + src/styles/_page.project.panel.scss | 2 +- src/styles/_page.project.panel.settings.scss | 14 +- 19 files changed, 628 insertions(+), 481 deletions(-) create mode 100644 resources/sql/20260808_account_password.sql create mode 100644 src/components/projectAccount.vue delete mode 100644 src/components/projectNewsletter.vue diff --git a/lib/Controller.php b/lib/Controller.php index 8089f62..ce53495 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -12,6 +12,8 @@ class Controller extends PhpObject 'add_post', 'subscribe', 'unsubscribe', + 'login', + 'logout', 'update_project', 'upload', 'add_comment', @@ -55,11 +57,15 @@ class Controller extends PhpObject $this->setReqVal('value', $asReq['value'] ?? ''); $this->setReqVal('type', $asReq['type'] ?? ''); $this->setReqVal('email', $asReq['email'] ?? ''); + $this->setReqVal('password', $asReq['password'] ?? ''); $this->setReqVal('latitude', $asReq['latitude'] ?? ''); $this->setReqVal('longitude', $asReq['longitude'] ?? ''); $this->setReqVal('timestamp', $asReq['timestamp'] ?? 0, 'positiveInt'); $this->setReqVal('csrf_token', $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['csrf_token'] ?? '')); + //Authentication and CSRF protection share the same server-side session. + $this->initCsrfToken(); + //Create Spot Instance $this->oSpot = new Spot($sProcessPage, $this->asReq['t']); $this->oSpot->setProjectId($this->asReq['id_project']); @@ -72,6 +78,7 @@ class Controller extends PhpObject //Clean errors $sDebug = ob_get_clean(); if($sDebug != '') $this->oSpot->addUncaughtError($sDebug); + if(session_status() === PHP_SESSION_ACTIVE) session_write_close(); return $sResult; } @@ -103,16 +110,13 @@ class Controller extends PhpObject { if(PHP_SAPI === 'cli') return; - $bCloseSession = false; if(session_status() !== PHP_SESSION_ACTIVE) { $bSecure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'); session_set_cookie_params(array('httponly' => true, 'secure' => $bSecure, 'samesite' => 'Lax')); session_start(); - $bCloseSession = true; } $this->setCsrfToken(); - if($bCloseSession) session_write_close(); } private function checkCsrfToken(string $sClientToken): bool @@ -130,9 +134,10 @@ class Controller extends PhpObject 'next_feed' => $this->oSpot->getNextFeed($this->asReq['id']), 'new_feed' => $this->oSpot->getNewFeed($this->asReq['id']), 'add_post' => $this->oSpot->addPost($this->asReq['name'], $this->asReq['content']), - 'subscribe' => $this->oSpot->subscribe($this->asReq['email'], $this->asReq['name']), + 'subscribe' => $this->oSpot->subscribe(), 'unsubscribe' => $this->oSpot->unsubscribe(), - 'unsubscribe_email' => $this->oSpot->unsubscribeFromEmail($this->asReq['id_entity']), + 'login' => $this->oSpot->login($this->asReq['email'], $this->asReq['password'], $this->asReq['name']), + 'logout' => $this->oSpot->logout(), 'update_project' => $this->oSpot->updateProject(), default => $this->dispatchAdmin($sAction) }; diff --git a/lib/Spot.php b/lib/Spot.php index 4f352c8..cab461f 100755 --- a/lib/Spot.php +++ b/lib/Spot.php @@ -76,7 +76,7 @@ class Spot extends Main 'email' => 'admin@admin.com', 'language' => self::DEFAULT_LANG, 'timezone' => date_default_timezone_get(), - 'active' => User::USER_ACTIVE, + 'subscribed'=> User::USER_SUBSCRIBED, 'clearance' => User::CLEARANCE_ADMIN )); $this->oUser->setUserId($iUserId); @@ -94,13 +94,12 @@ class Spot extends Main Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to'), self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), Db::getId(User::USER_TABLE), 'name', 'content', 'site_time', 'timezone'), Media::MEDIA_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'timezone', 'latitude', 'longitude', 'altitude', 'width', 'height', 'rotate', 'comment'), - User::USER_TABLE => array('name', 'email', 'gravatar', 'language', 'timezone', 'active', 'clearance'), + User::USER_TABLE => array('name', 'email', 'password', 'token', 'token_exp', 'gravatar', 'language', 'timezone', 'subscribed', 'clearance'), Map::MAP_TABLE => array('codename', 'pattern', 'token', 'tile_size', 'min_zoom', 'max_zoom', 'attribution'), Map::MAPPING_TABLE => array(Db::getId(Map::MAP_TABLE) , Db::getId(Project::PROJ_TABLE)) ), 'types' => array ( - 'active' => "BOOLEAN DEFAULT ".User::USER_INACTIVE, 'clearance' => "TINYINT(1) DEFAULT ".User::CLEARANCE_USER, 'active_from' => "TIMESTAMP DEFAULT 0", 'active_to' => "TIMESTAMP DEFAULT 0", @@ -120,6 +119,7 @@ class Spot extends Main 'model' => "VARCHAR(20)", 'name' => "VARCHAR(100)", 'pattern' => "VARCHAR(200) NOT NULL", + 'password' => "VARCHAR(255) NOT NULL DEFAULT ''", 'posted_on' => "TIMESTAMP DEFAULT 0", 'ref_feed_id' => "VARCHAR(40)", 'ref_msg_id' => "VARCHAR(15)", @@ -127,9 +127,11 @@ class Spot extends Main 'rotate' => "SMALLINT", 'site_time' => "TIMESTAMP DEFAULT 0", //DEFAULT 0 removes auto-set to current time 'status' => "VARCHAR(10)", + 'subscribed' => "BOOLEAN DEFAULT ".User::USER_UNSUBSCRIBED, 'taken_on' => "TIMESTAMP DEFAULT 0", 'timezone' => "CHAR(64) NOT NULL", //see mysql.time_zone_name 'token' => "VARCHAR(4096)", + 'token_exp' => "TIMESTAMP DEFAULT 0", 'type' => "VARCHAR(20)", 'unix_time' => "INT", 'min_zoom' => "TINYINT UNSIGNED", @@ -282,7 +284,7 @@ class Spot extends Main private function sendEmail() { $oEmail = new Email($this->asContext['serv_name'], 'email.update'); - $oEmail->setDestInfo($this->oUser->getActiveUsersInfo()); + $oEmail->setDestInfo($this->oUser->getSubscribedUsersInfo()); //Add Position $asSpotMessages = $this->getSpotMessages(array($this->oProject->getLastMessageId($this->getFeedConstraints(Feed::MSG_TABLE)))); @@ -377,32 +379,32 @@ class Spot extends Main return self::getJsonResult(true, '', $asLastUpdate); } - public function subscribe($sEmail, $sNickName) { - $asResult = $this->oUser->addUser($sEmail, $this->oLang->getLanguage(), date_default_timezone_get(), $sNickName); - $asUserInfo = $this->oUser->getUserInfo(); + public function login($sEmail, $sPassword, $sNickName) { + $asResult = $this->oUser->login($sEmail, $sPassword, $this->oLang->getLanguage(), date_default_timezone_get(), $sNickName); - //Send Confirmation Email - if($asResult['result'] && $asResult['desc']=='lang:newsletter.subscribed' && !Settings::DEBUG) { + if($asResult['result'] && $asResult['desc'] == 'subscribe_user') return $this->subscribe(); + else return self::getJsonResult($asResult['result'], $asResult['desc'], $this->oUser->getUserInfo()); + } + + public function logout() { + $asResult = $this->oUser->logout(); + return self::getJsonResult($asResult['result'], $asResult['desc'], User::DEFAULT_USER); + } + + public function subscribe() { + $asResult = $this->oUser->setSubscription(true); + $asUserInfo = $this->oUser->getUserInfo(); + if($asResult['result'] && !Settings::DEBUG) { $oConfEmail = new Email($this->asContext['serv_name'], 'email.confirmation'); $oConfEmail->setDestInfo($asUserInfo); $oConfEmail->send(); } - return self::getJsonResult($asResult['result'], $asResult['desc'], $asUserInfo); } public function unsubscribe() { - $asResult = $this->oUser->removeUser(); - return self::getJsonResult($asResult['result'], $asResult['desc'], User::DEFAULT_USER); - } - - public function unsubscribeFromEmail($iUserId) { - $this->oUser->setUserId($iUserId); - $this->oLang->setLanguage($this->oUser->getLang(), self::DEFAULT_LANG); - $asResult = $this->oUser->removeUser(); - - $sDesc = explode(':', $asResult['desc'])[1]; - return $this->oLang->getTranslation($sDesc); + $asResult = $this->oUser->setSubscription(false); + return self::getJsonResult($asResult['result'], $asResult['desc'], $this->oUser->getUserInfo()); } private function getSpotMessages($asMsgIds=array()) @@ -726,7 +728,7 @@ class Spot extends Main 'project' => $this->oProject->getProjects(), 'feed' => $oFeed->getFeeds(), 'spot' => $oFeed->getSpots(), - 'user' => $this->oUser->getActiveUsersInfo() + 'user' => $this->oUser->getSubscribedUsersInfo() ); foreach($asData['project'] as &$asProject) { @@ -794,7 +796,7 @@ class Spot extends Main default: $sDesc = $this->oLang->getTranslation('error.unknown_field', $sField); } - $asResult = $this->oUser->getActiveUserInfo($iId); + $asResult = $this->oUser->getUserById($iId); break; } if(!$bSuccess && $sDesc=='') $sDesc = Mask::LANG_PREFIX.'error.commit_db'; @@ -852,11 +854,6 @@ class Spot extends Main $sDesc = $asResult['feed'][0]['desc']; $bSuccess = $asResult['feed'][0]['del']; break; - case 'user': - $asResult = array('user' => array($this->oUser->removeUser($iId))); - $sDesc = $asResult['user'][0]['desc']; - $bSuccess = $asResult['user'][0]['result']; - break; } return self::getJsonResult($bSuccess, $sDesc, $asResult); diff --git a/lib/User.php b/lib/User.php index c9ed1e9..cac2b53 100644 --- a/lib/User.php +++ b/lib/User.php @@ -11,14 +11,17 @@ class User extends PhpObject { const USER_TABLE = 'users'; //Clearance Levels - const USER_ACTIVE = 1; - const USER_INACTIVE = 0; const CLEARANCE_USER = 0; const CLEARANCE_ADMIN = 9; const CLEARANCES = array('user'=>self::CLEARANCE_USER, 'admin'=>self::CLEARANCE_ADMIN); - //Cookie - const COOKIE_ID_USER = 'subscriber'; + const USER_SUBSCRIBED = 1; + const USER_UNSUBSCRIBED = 0; + + //Session & Cookie + const SESSION_ID_USER = 'id_user'; + const SESSION_ADMIN = 'admin_authenticated'; + const COOKIE_TOKEN = 'login'; const COOKIE_DURATION = 60 * 60 * 24 * 365; //1 year const DEFAULT_USER = array( @@ -28,7 +31,7 @@ class User extends PhpObject { 'email' => '', 'language' => '', 'timezone' => '', - 'active' => self::USER_INACTIVE, + 'subscribed'=> self::USER_UNSUBSCRIBED, 'clearance' => self::CLEARANCE_USER ); @@ -46,8 +49,7 @@ class User extends PhpObject { parent::__construct(__CLASS__); $this->oDb = &$oDb; $this->setUserId(0); - $this->asUserInfo = self::DEFAULT_USER; - $this->checkUserCookie(); + $this->checkSession(); } public function getUserId() { @@ -56,9 +58,10 @@ class User extends PhpObject { public function setUserId($iUserId) { $this->iUserId = 0; + $this->asUserInfo = self::DEFAULT_USER; if($iUserId > 0) { - $asUser = $this->getActiveUserInfo($iUserId); + $asUser = $this->getUserById($iUserId); if(!empty($asUser)) { $this->iUserId = $iUserId; $this->asUserInfo = $asUser; @@ -70,27 +73,23 @@ class User extends PhpObject { return $this->asUserInfo; } - public function getActiveUserInfo($iUserId) { + public function getUserById($iUserId) { $asUsersInfo = array(); - if($iUserId > 0) $asUsersInfo = $this->getActiveUsersInfo($iUserId); + if($iUserId > 0) $asUsersInfo = $this->getUsersInfo($iUserId); return empty($asUsersInfo)?array():array_shift($asUsersInfo); } - public function getActiveUsersInfo($iUserId=-1) { + public function getUsersInfo($iUserId=-1) { //Mapping between user fields and DB fields $asSelect = array_keys($this->asUserInfo); $asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE)." AS id"; - //Non-admin cannot access clearance info - if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) unset($asSelect['clearance']); - $asInfo = array( 'select' => $asSelect, - 'from' => self::USER_TABLE, - 'constraint'=> array('active'=>self::USER_ACTIVE) + 'from' => self::USER_TABLE ); - if($iUserId != -1) $asInfo['constraint'][Db::getId(self::USER_TABLE)] = $iUserId; + if($iUserId != -1) $asInfo['constraint'] = array(Db::getId(self::USER_TABLE) => $iUserId); return $this->oDb->selectRows($asInfo); } @@ -99,71 +98,116 @@ class User extends PhpObject { return $this->asUserInfo['language']; } - public function addUser($sEmail, $sLang, $sTimezone, $sNickName='') { + private function addUser($sEmail, $sLang, $sTimezone, $sNickName='') { $bSuccess = false; $sDesc = ''; - $sEmail = trim($sEmail); - //Check Email availability - $iUserId = $this->oDb->selectValue(self::USER_TABLE, Db::getId(self::USER_TABLE), array('email'=>$sEmail, 'active'=>self::USER_ACTIVE)); + $iUserId = $this->oDb->insertRow( + self::USER_TABLE, + array('email'=>$sEmail, 'language'=>$sLang, 'timezone'=>$sTimezone) + ); - if($iUserId > 0) { - //Just log user in - $sDesc = 'lang:newsletter.email_exists'; - $bSuccess = true; + if($iUserId == 0) $sDesc = 'lang:error.commit_db'; + else $bSuccess = true; + + //Extra optional values + if($bSuccess) { + $this->updateNickname($sNickName); + $this->updateGravatar($iUserId, $sEmail); + } + + return Spot::getResult($bSuccess, $sDesc, [Db::getId(self::USER_TABLE) => $iUserId]); + } + + public function setSubscription($bSubscribed) { + if($this->getUserId() > 0) { + $iSubscribed = $bSubscribed?1:0; + $iUserId = $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('subscribed'=>$iSubscribed)); + if(!$iUserId) return Spot::getResult(false, 'lang:error.commit_db'); + $this->asUserInfo['subscribed'] = $iSubscribed; + return Spot::getResult(true, $iSubscribed?'lang:account.subscribed':'lang:account.unsubscribed'); + } + } + + public function getSubscribedUsersInfo() { + $asSelect = array_keys($this->asUserInfo); + $asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE).' AS id'; + return $this->oDb->selectRows(array( + 'select'=>$asSelect, + 'from'=>self::USER_TABLE, + 'constraint'=>array('subscribed'=>self::USER_SUBSCRIBED) + )); + } + + public function login($sEmail, $sPassword, $sLang, $sTimezone, $sNickName='') { + $bSuccess = false; + $sDesc = ''; + $sEmail = strtolower(trim($sEmail)); + + //Check email value + if(!filter_var($sEmail, FILTER_VALIDATE_EMAIL)) { + $sDesc = 'lang:account.invalid_email'; } else { - //Add/Reactivate user - $iUserId = $this->oDb->insertUpdateRow( + //Check Email presence in DB + $asDBUser = $this->oDb->selectRow( self::USER_TABLE, - array('email'=>$sEmail, 'language'=>$sLang, 'timezone'=>$sTimezone, 'active'=>self::USER_ACTIVE), - array('email') + array('email' => $sEmail), + array(Db::getId(self::USER_TABLE), 'password', 'clearance') ); + $iUserId = $asDBUser[Db::getId(self::USER_TABLE)] ?? 0; + + //User exists + if($iUserId > 0) { + //Is Admin + if($asDBUser['clearance'] >= self::CLEARANCE_ADMIN) { + //Request a password + if($sPassword === '') $sDesc = empty($asDBUser['password'])?'lang:account.set_password':'lang:account.password_required'; + + //Set password + elseif(empty($asDBUser['password'])) { + if(!$this->oDb->updateRow(self::USER_TABLE, $iUserId, array('password' => password_hash($sPassword, PASSWORD_DEFAULT)))) $sDesc = 'lang:error.commit_db'; + else { + $sDesc = 'lang:account.password_set'; + $bSuccess = true; + } + } - if($iUserId==0) $sDesc = 'lang:error.commit_db'; - else { - $sDesc = 'lang:newsletter.subscribed'; - $bSuccess = true; + //Check password + elseif(password_verify($sPassword, $asDBUser['password'])) { + $bSuccess = true; + $sDesc = 'lang:account.logged_in'; + } + else $sDesc = 'lang:account.invalid_credentials'; + + //die(print_r(['provided_pass'=>$sPassword, 'db_pass'=>$asDBUser['password']], true)); + } + else $bSuccess = true; } + else { + //Unknown user, create it + $asAddResult = $this->addUser($sEmail, $sLang, $sTimezone, $sNickName); + $bSuccess = $asAddResult['result']; + $sDesc = $bSuccess?'subscribe_user':$asAddResult['desc']; + $iUserId = $asAddResult['data'][Db::getId(self::USER_TABLE)] ?? 0; + } } if($bSuccess) { $this->setUserId($iUserId); - - //Set Cookie (valid 1 year) - $this->updateCookie(self::COOKIE_DURATION); - - //Update Nickname if user has already posted - $this->updateNickname($sNickName); - - //Retrieve Gravatar image - $this->updateGravatar($iUserId, $sEmail); + $this->setSession(); + $this->setTokenCookie(); } return Spot::getResult($bSuccess, $sDesc); } - public function removeUser($iUserId=0) { - $iUserId = ($iUserId > 0)?$iUserId:$this->getUserId(); - $bSelf = ($iUserId == $this->getUserId()); - $bSuccess = false; - $sDesc = ''; - - if($bSelf || $this->checkUserClearance(self::CLEARANCE_ADMIN)) { - if($this->getUserId() > 0) { - $iUserId = $this->oDb->updateRow(self::USER_TABLE, $iUserId, array('active' => self::USER_INACTIVE)); - if($iUserId==0) $sDesc = 'lang:error.commit_db'; - else { - $sDesc = 'lang:newsletter.unsubscribed'; - if($bSelf) $this->updateCookie(-60 * 60); //Set Cookie in the past, deleting it - $bSuccess = true; - } - } - else $sDesc = 'lang:newsletter.unknown_email'; - } - else $sDesc = 'lang:error.no_auth'; - - return Spot::getResult($bSuccess, $sDesc); + public function logout() { + if($this->getUserId() > 0) $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('token' => '', 'token_exp' => '0000-00-00 00:00:00')); + $this->clearSession(); + $this->clearCookie(); + $this->setUserId(0); + return Spot::getResult(true, 'lang:account.logged_out'); } public function updateNickname($sNickname) { @@ -175,15 +219,6 @@ class User extends PhpObject { $this->oDb->updateRow(self::USER_TABLE, $iUserId, array('gravatar' => base64_encode($sImage))); } - private function checkUserCookie() { - if(isset($_COOKIE[self::COOKIE_ID_USER])){ - $this->setUserId($_COOKIE[self::COOKIE_ID_USER]); - - //Extend cookie life - if($this->getUserId() > 0) $this->updateCookie(self::COOKIE_DURATION); - } - } - public function checkUserClearance($iClearance) { return ($this->asUserInfo['clearance'] >= $iClearance); @@ -206,7 +241,96 @@ class User extends PhpObject { return Spot::getResult($bSuccess, $sDesc); } - private function updateCookie($iDeltaTime) { - setcookie(self::COOKIE_ID_USER, ($iDeltaTime < 0)?'':$this->getUserId(), array('samesite' => 'Lax', 'expires' => time() + $iDeltaTime)); + /* Session */ + + private function checkSession() { + $iUserId = (int) ($_SESSION[self::SESSION_ID_USER] ?? 0); + if($iUserId > 0) { + $this->setUserId($iUserId); + if($this->checkUserClearance(self::CLEARANCE_ADMIN) && empty($_SESSION[self::SESSION_ADMIN])) $this->logout(); + } + else $this->checkTokenCookie(); + } + + private function setSession() { + session_regenerate_id(true); + $_SESSION[self::SESSION_ID_USER] = $this->getUserId(); + $_SESSION[self::SESSION_ADMIN] = $this->checkUserClearance(self::CLEARANCE_ADMIN); + } + + private function clearSession() { + unset($_SESSION[self::SESSION_ID_USER], $_SESSION[self::SESSION_ADMIN]); + } + + /* Cookie */ + + private function checkTokenCookie() { + $sCookieValue = $this->getCookie(); + + //Check cookie structure + $asToken = explode(':', $sCookieValue, 3); + $iUserId = $asToken[0]; + if(count($asToken) !== 3 || !ctype_digit($iUserId) || !ctype_digit($asToken[1]) || !preg_match('/^[a-f0-9]{64}$/', $asToken[2])) { + if($sCookieValue !== '') $this->clearCookie(); + return; + } + + //Get user info + $asUser = $this->oDb->selectRow( + self::USER_TABLE, + $iUserId, + array('clearance', 'token', 'token_exp') + ); + + //Check token value + if(!empty($asUser) + && $asUser['clearance'] == $asToken[1] + && time() <= strtotime($asUser['token_exp']) + && hash_equals($asUser['token'], hash('sha256', $sCookieValue)) + ) { + $this->setUserId($iUserId); + $this->setSession(); + } + else $this->clearCookie(); + } + + private function setTokenCookie() { + $sToken = bin2hex(random_bytes(32)); + $iClearance = $this->asUserInfo['clearance']; + $sCookieValue = implode(':', [$this->getUserId(), $iClearance, $sToken]); + + $this->oDb->updateRow( + self::USER_TABLE, + $this->getUserId(), + array( + 'token' => hash('sha256', $sCookieValue), + 'token_exp' => date(Db::TIMESTAMP_FORMAT, time() + self::COOKIE_DURATION) + ) + ); + + $this->setCookie($sCookieValue, time() + self::COOKIE_DURATION); + } + + private function getCookie() { + return $_COOKIE[self::COOKIE_TOKEN] ?? ''; + } + + private function setCookie($sValue, $iExpires) { + setcookie( + self::COOKIE_TOKEN, + $sValue, + array( + 'expires' => $iExpires, + 'path' => '/', + 'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'), + 'httponly' => true, + 'samesite' => 'Lax' + ) + ); + } + + private function clearCookie() { + $this->setCookie('', time() - 3600); + unset($_COOKIE[self::COOKIE_TOKEN]); } } diff --git a/package-lock.json b/package-lock.json index baa21f0..d647019 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,12 +43,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -58,9 +58,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -70,40 +70,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.3.1.tgz", @@ -239,29 +205,10 @@ "pbf": "^5.1.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", "dev": true, "license": "MIT", "funding": { @@ -562,9 +509,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", + "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", "cpu": [ "arm64" ], @@ -579,9 +526,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", "cpu": [ "arm64" ], @@ -596,9 +543,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", "cpu": [ "x64" ], @@ -613,9 +560,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", + "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", "cpu": [ "x64" ], @@ -630,9 +577,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", + "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", "cpu": [ "arm" ], @@ -647,9 +594,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", + "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", "cpu": [ "arm64" ], @@ -667,9 +614,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", + "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", "cpu": [ "arm64" ], @@ -687,9 +634,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", + "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", "cpu": [ "ppc64" ], @@ -707,9 +654,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", + "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", "cpu": [ "s390x" ], @@ -727,9 +674,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", + "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", "cpu": [ "x64" ], @@ -747,9 +694,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", + "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", "cpu": [ "x64" ], @@ -767,9 +714,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", + "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", "cpu": [ "arm64" ], @@ -783,29 +730,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", + "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", "cpu": [ "arm64" ], @@ -820,9 +748,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", + "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", "cpu": [ "x64" ], @@ -849,17 +777,6 @@ "integrity": "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA==", "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -949,39 +866,39 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", - "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.40", + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", - "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.40", - "@vue/shared": "3.5.40" + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", - "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.40", - "@vue/compiler-dom": "3.5.40", - "@vue/compiler-ssr": "3.5.40", - "@vue/shared": "3.5.40", + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", @@ -989,61 +906,61 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", - "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.40", - "@vue/shared": "3.5.40" + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/reactivity": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", - "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.40" + "@vue/shared": "3.5.41" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", - "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.40", - "@vue/shared": "3.5.40" + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", - "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.40", - "@vue/runtime-core": "3.5.40", - "@vue/shared": "3.5.40", + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", - "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.40", - "@vue/runtime-dom": "3.5.40", - "@vue/shared": "3.5.40" + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/shared": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", - "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", "license": "MIT" }, "node_modules/autosize": { @@ -1494,9 +1411,9 @@ } }, "node_modules/maplibre-gl": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-6.0.0.tgz", - "integrity": "sha512-1wBgEhzTZfA+cDpFdt0fM1mJA5mJ90fifORJ7D8JcKLJCvPT/iTx9bNxkP7DqEYde65DJd7gE7h83khwNRqdyg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-6.2.0.tgz", + "integrity": "sha512-PaNYtxWmYgIdDHshXsnU3Pho+H9IPme9H6dTjZbFWNazi+Q5QgKgIlu2GiSGVtOZ77/Fz3n5/1/kGM6ETzu0Lg==", "license": "BSD-3-Clause", "dependencies": { "@mapbox/point-geometry": "^1.1.0", @@ -1504,7 +1421,7 @@ "@mapbox/unitbezier": "^1.0.0", "@mapbox/vector-tile": "^3.0.0", "@maplibre/geojson-vt": "^6.1.1", - "@maplibre/maplibre-gl-style-spec": "^26.1.0", + "@maplibre/maplibre-gl-style-spec": "^26.2.1", "@maplibre/mlt": "^1.1.12", "@maplibre/vt-pbf": "^4.3.2", "@types/geojson": "^7946.0.16", @@ -1629,9 +1546,9 @@ } }, "node_modules/postcss": { - "version": "8.5.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", - "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -1648,7 +1565,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1657,9 +1574,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -1681,9 +1598,9 @@ "license": "ISC" }, "node_modules/preact": { - "version": "10.29.7", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz", - "integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==", + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", "license": "MIT", "funding": { "type": "opencollective", @@ -1711,9 +1628,9 @@ "license": "ISC" }, "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -1742,13 +1659,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", + "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.139.0", + "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -1758,21 +1675,20 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" + "@rolldown/binding-android-arm64": "1.2.3", + "@rolldown/binding-darwin-arm64": "1.2.3", + "@rolldown/binding-darwin-x64": "1.2.3", + "@rolldown/binding-freebsd-x64": "1.2.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", + "@rolldown/binding-linux-arm64-gnu": "1.2.3", + "@rolldown/binding-linux-arm64-musl": "1.2.3", + "@rolldown/binding-linux-ppc64-gnu": "1.2.3", + "@rolldown/binding-linux-s390x-gnu": "1.2.3", + "@rolldown/binding-linux-x64-gnu": "1.2.3", + "@rolldown/binding-linux-x64-musl": "1.2.3", + "@rolldown/binding-openharmony-arm64": "1.2.3", + "@rolldown/binding-win32-arm64-msvc": "1.2.3", + "@rolldown/binding-win32-x64-msvc": "1.2.3" } }, "node_modules/sass": { @@ -1876,25 +1792,17 @@ "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", "license": "ISC" }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/vite": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", - "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", + "lightningcss": "^1.33.0", "picomatch": "^4.0.5", - "postcss": "^8.5.17", - "rolldown": "~1.1.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "bin": { @@ -1911,7 +1819,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -1963,16 +1871,16 @@ } }, "node_modules/vue": { - "version": "3.5.40", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", - "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.40", - "@vue/compiler-sfc": "3.5.40", - "@vue/runtime-dom": "3.5.40", - "@vue/server-renderer": "3.5.40", - "@vue/shared": "3.5.40" + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" diff --git a/resources/lang/en.json b/resources/lang/en.json index 6220f23..9ad8dc0 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -5,6 +5,31 @@ "save": "Save", "send": "Send" }, + "account": { + "login": "Log in", + "logout": "Log out", + "confirm_password": "Confirm password", + "email_placeholder": "my@email.com", + "invalid_credentials": "Incorrect password", + "invalid_email": "This doesn't look like a valid email address", + "logged_in": "Logged in", + "logged_out": "Logged out", + "newsletter": "Email me new updates", + "password": "Password", + "password_mismatch": "The passwords don't match", + "password_required": "Enter your password to continue", + "password_set": "Your password has been set successfully.", + "set_password": "Set a password for this administrator account", + "subscribed": "Thanks! You'll receive a confirmation email shortly.", + "unsubscribed": "Done. No more junk mail from me.", + "title": "Account", + "status": { + "logged_in": "You're logged in.", + "logged_in_named": "Hello $0, you're logged in.", + "logged_out": "Enter your email address below to receive my position as soon as I find it :)", + "subscribed": "You're all set. I'll send you updates!" + } + }, "admin": { "config": "Settings", "create_success": "Created", @@ -85,19 +110,6 @@ "locale": "en_NZ", "page_og_desc": "Stay in touch while I'm away hiking." }, - "newsletter": { - "email_exists": "This email address is already subscribed. You can unsubscribe by clicking the button above.", - "email_placeholder": "my@email.com", - "invalid_email": "This doesn't look like a valid email address", - "subscribe": "Subscribe", - "subscribed": "Thanks! You'll receive a confirmation email shortly.", - "subscribed_desc": "You're all set. I'll send you updates!", - "title": "Keep in touch!", - "unknown_email": "Unknown email address", - "unsubscribe": "Unsubscribe", - "unsubscribed": "Done. No more junk mail from me.", - "unsubscribed_desc": "Enter your email address to receive my position as soon as I find it :)" - }, "post": { "copy_to_clipboard": "Copy direct link to clipboard", "link_copied": "Link copied!", @@ -176,8 +188,9 @@ "success": "$0 uploaded successfully" }, "user": { - "active": "Active users", + "subscribed": "Subscribed users", "clearance": "Clearance", + "email": "Email", "id": "User ID", "language": "Language", "name": "User name" diff --git a/resources/lang/es.json b/resources/lang/es.json index d21f81c..9f1b83f 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -5,6 +5,31 @@ "save": "Guardar", "send": "Enviar" }, + "account": { + "login": "Iniciar sesión", + "logout": "Cerrar sesión", + "confirm_password": "Confirmar contraseña", + "email_placeholder": "nombre@email.com", + "invalid_credentials": "Contraseña incorrecta", + "invalid_email": "Esto no parece una dirección de correo electrónico", + "logged_in": "Sesión iniciada", + "logged_out": "Sesión cerrada", + "newsletter": "Enviarme nuevas actualizaciones por correo", + "password": "Contraseña", + "password_mismatch": "Las contraseñas no coinciden", + "password_required": "Introduce tu contraseña para continuar", + "password_set": "Tu contraseña se ha establecido correctamente.", + "set_password": "Establece una contraseña para esta cuenta administradora", + "subscribed": "¡Gracias! Recibirás un correo electrónico de confirmación.", + "unsubscribed": "Listo. ¡No más spam!", + "title": "Cuenta", + "status": { + "logged_in": "Has iniciado sesión.", + "logged_in_named": "Hola $0, has iniciado sesión.", + "logged_out": "Añade tu dirección de correo electrónico y te enviaré mi posición actualizada tan pronto como la reciba :)", + "subscribed": "Todo está listo. Te enviaré noticias frescas en cuanto las reciba. Prometido..." + } + }, "admin": { "config": "Configuración", "create_success": "Creado", @@ -85,19 +110,6 @@ "locale": "es_ES", "page_og_desc": "Mantente en contacto conmigo durante mis aventuras en la montaña." }, - "newsletter": { - "email_exists": "Esta dirección de correo electrónico ya está registrada. Puedes darte de baja haciendo clic en el botón de arriba.", - "email_placeholder": "nombre@email.com", - "invalid_email": "Esto no parece una dirección de correo electrónico", - "subscribe": "Suscribirse", - "subscribed": "¡Gracias! Recibirás un correo electrónico de confirmación.", - "subscribed_desc": "Todo está listo. Te enviaré noticias frescas en cuanto las reciba. Prometido...", - "title": "Mantente en contacto", - "unknown_email": "Dirección de correo electrónico desconocida", - "unsubscribe": "Darse de baja", - "unsubscribed": "Listo. ¡No más spam!", - "unsubscribed_desc": "Añade tu dirección de correo electrónico y te enviaré mi posición actualizada tan pronto como la reciba :)" - }, "post": { "copy_to_clipboard": "Copiar el enlace", "link_copied": "¡Enlace copiado!", @@ -176,8 +188,9 @@ "success": "$0 se ha subido correctamente." }, "user": { - "active": "Usuarios activos", + "subscribed": "Usuarios suscritos", "clearance": "Nivel de autorización", + "email": "Correo electrónico", "id": "ID del usuario", "language": "Idioma", "name": "Nombre" diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 7bbdda6..5f556f2 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -5,6 +5,31 @@ "save": "Sauvegarder", "send": "Envoyer" }, + "account": { + "login": "Se connecter", + "logout": "Se déconnecter", + "confirm_password": "Confirmer le mot de passe", + "email_placeholder": "mon@email.com", + "invalid_credentials": "Mot de passe incorrect", + "invalid_email": "Ceci ne ressemble pas à une adresse e-mail", + "logged_in": "Connexion réussie", + "logged_out": "Déconnexion réussie", + "newsletter": "M'envoyer les nouvelles mises à jour par e-mail", + "password": "Mot de passe", + "password_mismatch": "Les mots de passe ne correspondent pas", + "password_required": "Saisis ton mot de passe pour continuer", + "password_set": "Ton mot de passe a bien été défini.", + "set_password": "Définis un mot de passe pour ce compte administrateur", + "subscribed": "Merci ! Tu vas recevoir un e-mail de confirmation très bientôt.", + "unsubscribed": "C'est fait. Fini le spam !", + "title": "Compte", + "status": { + "logged_in": "Tu es connecté.", + "logged_in_named": "Bonjour $0, tu es connecté.", + "logged_out": "Ajoute ton adresse e-mail et je t'enverrai ma nouvelle position dès que je la trouve :)", + "subscribed": "C'est tout bon. Je t'enverrai des nouvelles fraîches. Parole de scout." + } + }, "admin": { "config": "Paramètres", "create_success": "Créé", @@ -85,19 +110,6 @@ "locale": "fr_CH", "page_og_desc": "Garde le contact lorsque je suis sur les chemins." }, - "newsletter": { - "email_exists": "Cette adresse e-mail est déjà enregistrée. Tu peux te désinscrire en cliquant sur le bouton ci-dessus.", - "email_placeholder": "mon@email.com", - "invalid_email": "Ceci ne ressemble pas à une adresse e-mail", - "subscribe": "S'abonner", - "subscribed": "Merci ! Tu vas recevoir un e-mail de confirmation très bientôt.", - "subscribed_desc": "C'est tout bon. Je t'enverrai des nouvelles fraîches. Parole de scout.", - "title": "Rester en contact", - "unknown_email": "Adresse e-mail inconnue", - "unsubscribe": "Se désinscrire", - "unsubscribed": "C'est fait. Fini le spam !", - "unsubscribed_desc": "Ajoute ton adresse e-mail et je t'enverrai ma nouvelle position dès que je la trouve :)" - }, "post": { "copy_to_clipboard": "Copier le lien dans le presse-papiers", "link_copied": "Lien copié !", @@ -176,8 +188,9 @@ "success": "$0 a été téléversé" }, "user": { - "active": "Utilisateurs actifs", + "subscribed": "Utilisateurs abonnés", "clearance": "Niveau d'autorisation", + "email": "E-mail", "id": "ID utilisateur", "language": "Langue", "name": "Nom" diff --git a/resources/sql/20260808_account_password.sql b/resources/sql/20260808_account_password.sql new file mode 100644 index 0000000..76c289d --- /dev/null +++ b/resources/sql/20260808_account_password.sql @@ -0,0 +1,7 @@ +ALTER TABLE `users` + ADD COLUMN `password` VARCHAR(255) NOT NULL DEFAULT '' AFTER `email`, + ADD COLUMN `token` VARCHAR(4096) AFTER `password`, + ADD COLUMN `token_exp` TIMESTAMP DEFAULT 0 AFTER `token`; + +ALTER TABLE `users` + CHANGE COLUMN `active` `subscribed` BOOLEAN DEFAULT 0; diff --git a/src/components/admin.vue b/src/components/admin.vue index 69a6958..bd2e3a8 100644 --- a/src/components/admin.vue +++ b/src/components/admin.vue @@ -115,7 +115,7 @@ export default {