Add the feature to change theprofile Picture. Need to be secured
continuous-integration/drone/push Build is failing Details

d_yanis 1 year ago
parent 634b9cba65
commit 7877fb6e33

@ -13,113 +13,154 @@ import Col from 'react-bootstrap/Col';
import { updateSourceFile } from "typescript"; import { updateSourceFile } from "typescript";
import { fetchAPI } from "../Fetcher"; import { fetchAPI } from "../Fetcher";
import { fetchPOST } from "../Fetcher"; import { fetchPOST } from "../Fetcher";
import axios from "axios";
import Modal from 'react-bootstrap/Modal';
import { Stack } from "react-bootstrap";
import * as http from 'follow-redirects/http';
import * as https from 'follow-redirects/https';
// import fetch from 'node-fetch';
export default function Settings({ user }: { user: User }) { export default function Settings({ user }: { user: User }) {
return ( return (
<div id="main"> <div id="main">
<Header username={user.name} /> <Header username={user.name} />
<Body user={user} /> <Body user={user} />
</div> </div>
) )
} }
function Body({ user }: { user: User }) { function Body({ user }: { user: User }) {
return ( return (
<div id="body"> <div id="body">
<div id="content"> <div id="content">
<MainTitle title="Paramètres" id={null} /> <MainTitle title="Paramètres" id={null} />
<AccountSettings user={user} /> <AccountSettings user={user} />
</div> </div>
</div> </div>
) );
} }
function AccountSettings({ user }: { user: User }) { function AccountSettings({ user }: { user: User }) {
return ( return (
<div id="account"> <div id="account">
<SecondTitle title="Compte personnel" id={null} /> <SecondTitle title="Compte personnel" id={null} />
<ProfilSettings user={user} /> <ProfilSettings user={user} />
</div> </div>
); );
} }
function ProfilSettings({ user }: { user: User }) { function ProfilSettings({ user }: { user: User }) {
// Utilisez useState pour gérer l'état du champ de saisie const nameRef = useRef<HTMLInputElement>(null);
// const [username, setUsername] = useState({user.username}); const emailRef = useRef<HTMLInputElement>(null);
const [modalShow, setModalShow] = useState(false);
const width = 140;
const profilePicture = user.profilePicture;
console.log("profile :" + profilePicture);
return (
<Container>
<Row>
<Col>
<Stack>
<Image src={profilePicture} width={width} height={width} roundedCircle />
<Button variant="outline-primary" onClick={() => setModalShow(true)}>Changer photo de profil</Button>
<MyVerticallyCenteredModal
show={modalShow}
onHide={() => setModalShow(false)}
/>
</Stack>
// // Fonction pour mettre à jour l'état lorsqu'il y a un changement dans le champ de saisie </Col>
// const handleUsernameChange = (event : ChangeEvent<HTMLInputElement>) => { <Col>
// setUsername(event.target.value); <Form>
// }; <Form.Group className="mb-3" controlId="formUsername">
<Form.Label className="content">Nom d'utilisateur</Form.Label>
<Form.Control ref={nameRef} size="sm" defaultValue={user.name} />
</Form.Group>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label className="content">Adresse mail</Form.Label>
<Form.Control ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" />
{/* <Form.Control readOnly onClick={() => alert("En cours de développement...")} ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" /> */}
</Form.Group>
<Button variant="outline-primary" type="button" onClick={() => updateAccountInfos(nameRef.current!.value, emailRef.current!.value)}>Mettre à jour</Button>
</Form>
</Col>
</Row>
</Container>
);
}
// return ( function reload() {
// <form id="account-content"> fetchPOST("session/update", {});
// <dl> location.reload();
// <dt>Nom d'utilisateur</dt> }
// {/* Utilisez la valeur de l'état et la fonction onChange */}
// <dd><input type="text" value={username} onChange={handleUsernameChange} /></dd>
// </dl>
// </form>
// );
const nameRef = useRef<HTMLInputElement>(null); function updateAccountInfos(name: string, email: string) {
const emailRef = useRef<HTMLInputElement>(null); fetchAPI("account/update/profile", {
name: name,
email: email
});
reload();
}
const width = 140; function updateAccountPicture(lien: string) {
const profilePicture = user.profilePicture; fetchAPI("account/update/profilePicture", {
console.log("profile :" + profilePicture); lien: lien
return ( });
<Container> reload();
<Row> }
<Col>
<Image src={profilePicture} width={width} roundedCircle />
<Button variant="outline-primary" onClick={() => alert("En cours de développement...")}>Changer photo de profil</Button>
</Col>
<Col>
<Form>
<Form.Group className="mb-3" controlId="formUsername">
<Form.Label className="content">Nom d'utilisateur</Form.Label>
<Form.Control ref={nameRef} size="sm" defaultValue={user.name} />
</Form.Group>
<Form.Group className="mb-3" controlId="formEmail">
<Form.Label className="content">Adresse mail</Form.Label>
<Form.Control ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" />
{/* <Form.Control readOnly onClick={() => alert("En cours de développement...")} ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" /> */}
</Form.Group>
<Button variant="outline-primary" type="button" onClick={() => updateAccountInfos(nameRef.current!.value, emailRef.current!.value)}>Mettre à jour</Button>
</Form>
</Col>
</Row> function MyVerticallyCenteredModal(props: any) {
</Container> const urlRef = useRef<HTMLInputElement>(null);
// <div id="account"> return (
// <div id="profil-picture"> <Modal
// <Image src={profilePicture}roundedCircle /> {...props}
// <Button variant="outline-primary">Changer photo de profil</Button> size="lg"
// </div> aria-labelledby="title-modal"
// <div id="account-infos"> centered
// <Form> >
// <Form.Group className="mb-3" controlId="formUsername"> <Modal.Header>
// <Form.Label className="content">Nom d'utilisateur</Form.Label> <Modal.Title id="title-modal" >
// <Form.Control ref={nameRef} size="sm" defaultValue={user.name}/> Nouvelle photo de profil
// </Form.Group> </Modal.Title>
// <Form.Group className="mb-3" controlId="formEmail"> </Modal.Header>
// <Form.Label className="content">Adresse mail</Form.Label> <Modal.Body>
// <Form.Control readOnly onClick={() => alert("En cours de développement...")} ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" /> <Form.Label>Nouvelle image</Form.Label>
// </Form.Group> <Form.Control ref={urlRef} type="input" />
// <Button variant="outline-primary" type="button" onClick={() => updateAccountInfos(nameRef.current!.value, emailRef.current!.value, user)}>Mettre à jour</Button> </Modal.Body>
// </Form> <Modal.Footer>
// </div> <Button onClick={props.onHide}>Annuler</Button>
// </div> <Button onClick={() => handleNewImage(urlRef.current!.value)}>Valider</Button>
); </Modal.Footer>
</Modal>
);
} }
function updateAccountInfos(name: string, email: string) { async function handleNewImage(lien: string) {
fetchAPI("account/update/profile", { let exist = await testImage(lien);
name: name, console.log(exist);
email: email if (exist) {
}); updateAccountPicture(lien);
fetchPOST("account/update", {}); }
location.reload();
} }
async function testImage(lien: string) {
// try {
// const response = await axios.head(lien);
// console.log(response);
// // Vérifier le statut de la réponse (200 OK est considéré comme valide)
// if (response.status === 200) {
// // Vérifier le type de contenu pour s'assurer qu'il s'agit d'une image
// const contentType = response.headers['content-type'];
// if (contentType && contentType.startsWith('image/')) {
// return true;
// }
// return true;
// }
// return false;
// } catch (error) {
// console.error("Erreur lors de la requête HEAD:", error);
// return false;
// }
return true;
}

@ -10,7 +10,9 @@
"@types/node": "^16.18.59", "@types/node": "^16.18.59",
"@types/react": "^18.2.31", "@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14", "@types/react-dom": "^18.2.14",
"axios": "^1.6.7",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"node-fetch": "^3.3.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.10.0", "react-bootstrap": "^2.10.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -33,6 +35,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/follow-redirects": "^1.14.4",
"@vitejs/plugin-react": "^4.1.0", "@vitejs/plugin-react": "^4.1.0",
"prettier": "^3.1.0", "prettier": "^3.1.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",

@ -38,8 +38,9 @@ function getRoutes(): AltoRouter {
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); $router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc))); $router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc)));
$router->map("POST", "/account/update/profile", Action::auth(fn(Account $acc) => getAPIUserController()->updateProfile($acc))); $router->map("POST", "/account/update/profile", Action::auth(fn(Account $acc) => getAPIUserController()->updateProfile($acc)));
$router->map("POST", "/account/update/profilePicture", Action::auth(fn(Account $acc) => getAPIUserController()->updatePicture($acc)));
return $router; return $router;
} }
/** /**

@ -107,7 +107,7 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/team/[i:idTeam]/remove/[i:idMember]", Action::auth(fn(int $idTeam, int $idMember, SessionHandle $s) => getTeamController()->deleteMember($idTeam, $idMember, $s))); $ar->map("GET", "/team/[i:idTeam]/remove/[i:idMember]", Action::auth(fn(int $idTeam, int $idMember, SessionHandle $s) => getTeamController()->deleteMember($idTeam, $idMember, $s)));
$ar->map("GET", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->displayEditTeam($idTeam, $s))); $ar->map("GET", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->displayEditTeam($idTeam, $s)));
$ar->map("POST", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->editTeam($idTeam, $_POST, $s))); $ar->map("POST", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->editTeam($idTeam, $_POST, $s)));
$ar->map("POST", "/account/update", Action::auth(fn(SessionHandle $s) => getAuthController()->updateAccount($s))); $ar->map("POST", "/session/update", Action::auth(fn(SessionHandle $s) => getAuthController()->updateAccount($s)));
return $ar; return $ar;
} }

@ -2,6 +2,7 @@
namespace IQBall\Api\Controller; namespace IQBall\Api\Controller;
use Exception;
use IQBall\App\Control; use IQBall\App\Control;
use IQBall\Core\Data\Account; use IQBall\Core\Data\Account;
use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpCodes;
@ -31,7 +32,6 @@ class APIUserController {
* @return HttpResponse * @return HttpResponse
*/ */
public function updateProfile(Account $account): HttpResponse { public function updateProfile(Account $account): HttpResponse {
error_log("Test");
return Control::runChecked([ return Control::runChecked([
"name" => [Validators::name()], "name" => [Validators::name()],
"email" => [Validators::email()] "email" => [Validators::email()]
@ -47,8 +47,50 @@ class APIUserController {
return HttpResponse::fromCode(HttpCodes::OK); return HttpResponse::fromCode(HttpCodes::OK);
}); });
}
function testImage(string $lien) {
try {
$headers = get_headers($lien, 1);
// Vérifier le statut de la réponse (200 OK est considéré comme valide)
$statusCode = explode(' ', $headers[0])[1];
if ($statusCode === '200') {
// Vérifier le type de contenu pour s'assurer qu'il s'agit d'une image
$contentType = $headers['Content-Type'];
if ($contentType && strpos($contentType, 'image/') === 0) {
return true;
}
}
return false;
} catch (Exception $error) {
return false;
}
}
/**
* @param Account $account
* @return HttpResponse
*/
public function updatePicture(Account $account): HttpResponse {
return Control::runChecked([
"lien" => [Validators::everything()]
], function (HttpRequest $request) use ($account) {
error_log("test");
$failures = $this->model->updatePicture($request["lien"], $account->getUser()->getId());
if (!empty($failures)) {
//TODO find a system to handle Unauthorized error codes more easily from failures.
return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST);
}
return HttpResponse::fromCode(HttpCodes::OK);
});
// error_log("Test"); // error_log("Test");
// return new HttpResponse(HttpCodes::OK, []); // return new HttpResponse(HttpCodes::OK, []);
} }
} }

@ -163,4 +163,16 @@ class AccountGateway {
':id' => [$id, PDO::PARAM_STR] ':id' => [$id, PDO::PARAM_STR]
]); ]);
} }
public function changePicture(int $id, string $newLien) {
error_log($newLien);
$this->con->exec("
UPDATE Account
SET profilePicture = :lien
WHERE id = :id
", [
':lien' => [$newLien, PDO::PARAM_STR],
':id' => [$id, PDO::PARAM_STR]
]);
}
} }

@ -70,6 +70,14 @@ class AuthModel {
return [ValidationFail::error("Account doesn't exist")]; return [ValidationFail::error("Account doesn't exist")];
} }
public function updatePicture(string $lien, int $id) : array {
if(!empty($this->gateway->getAccountFromId($id))) {
$this->gateway->changePicture($id, $lien);
return [];
}
return [ValidationFail::error("Account doesn't exist")];
}
/** /**
* Generate a random base 64 string * Generate a random base 64 string
* @return string * @return string
@ -97,4 +105,4 @@ class AuthModel {
public function updateAccount(MutableSessionHandle $session, string $token) { public function updateAccount(MutableSessionHandle $session, string $token) {
$session->setAccount($this->gateway->getAccountFromToken($token)); $session->setAccount($this->gateway->getAccountFromToken($token));
} }
} }

@ -84,4 +84,16 @@ class Validators {
fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")] fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")]
); );
} }
}
public static function validateImageUrl(): Validator {
$urlPattern = '/\bhttps?:\/\/\S+?\.(?:jpg|jpeg|gif|png)\b/i';
return self::regex($urlPattern, "L'URL doit pointer vers une image (extension jpg, jpeg, gif, png).");
}
public static function everything(): Validator {
$pattern = '/.*/';
return self::regex($pattern, "L'URL doit pointer vers une image (extension jpg, jpeg, gif, png).");
}
}
Loading…
Cancel
Save