Bug Fixes + IN Operator + User deletion
This commit is contained in:
		
							parent
							
								
									e6361a3c91
								
							
						
					
					
						commit
						d8846ff132
					
				| @ -110,6 +110,7 @@ namespace Api\User { | |||||||
|   use Api\UserAPI; |   use Api\UserAPI; | ||||||
|   use DateTime; |   use DateTime; | ||||||
|   use Driver\SQL\Condition\Compare; |   use Driver\SQL\Condition\Compare; | ||||||
|  |   use Driver\SQL\Condition\CondIn; | ||||||
|   use Objects\User; |   use Objects\User; | ||||||
| 
 | 
 | ||||||
|   class Create extends UserAPI { |   class Create extends UserAPI { | ||||||
| @ -190,6 +191,28 @@ namespace Api\User { | |||||||
|       return $this->success; |       return $this->success; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private function selectIds($page, $count) { | ||||||
|  |       $sql = $this->user->getSQL(); | ||||||
|  |       $res = $sql->select("User.uid") | ||||||
|  |         ->from("User") | ||||||
|  |         ->limit($count) | ||||||
|  |         ->offset(($page - 1) * $count) | ||||||
|  |         ->orderBy("User.uid") | ||||||
|  |         ->ascending() | ||||||
|  |         ->execute(); | ||||||
|  | 
 | ||||||
|  |       $this->success = ($res !== NULL); | ||||||
|  |       $this->lastError = $sql->getLastError(); | ||||||
|  | 
 | ||||||
|  |       if ($this->success) { | ||||||
|  |         $ids = array(); | ||||||
|  |         foreach($res as $row) $ids[] = $row["uid"]; | ||||||
|  |         return $ids; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function execute($values = array()) { |     public function execute($values = array()) { | ||||||
|       if (!parent::execute($values)) { |       if (!parent::execute($values)) { | ||||||
|         return false; |         return false; | ||||||
| @ -209,16 +232,18 @@ namespace Api\User { | |||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       $userIds = $this->selectIds($page, $count); | ||||||
|  |       if ($userIds === false) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       $sql = $this->user->getSQL(); |       $sql = $this->user->getSQL(); | ||||||
|       $res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at", |       $res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at", | ||||||
|         "Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor") |         "Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor") | ||||||
|         ->from("User") |         ->from("User") | ||||||
|         ->leftJoin("UserGroup", "User.uid", "UserGroup.user_id") |         ->leftJoin("UserGroup", "User.uid", "UserGroup.user_id") | ||||||
|         ->leftJoin("Group", "Group.uid", "UserGroup.group_id") |         ->leftJoin("Group", "Group.uid", "UserGroup.group_id") | ||||||
|         ->orderBy("User.uid") |         ->where(new CondIn("User.uid", $userIds)) | ||||||
|         ->ascending() |  | ||||||
|         ->limit($count) |  | ||||||
|         ->offset(($page - 1) * $count) |  | ||||||
|         ->execute(); |         ->execute(); | ||||||
| 
 | 
 | ||||||
|       $this->success = ($res !== FALSE); |       $this->success = ($res !== FALSE); | ||||||
| @ -289,6 +314,7 @@ namespace Api\User { | |||||||
|           ); |           ); | ||||||
| 
 | 
 | ||||||
|           foreach($user as $row) { |           foreach($user as $row) { | ||||||
|  |             if (!is_null($row["groupId"])) { | ||||||
|               $this->result["user"]["groups"][$row["groupId"]] = array( |               $this->result["user"]["groups"][$row["groupId"]] = array( | ||||||
|                 "name" => $row["groupName"], |                 "name" => $row["groupName"], | ||||||
|                 "color" => $row["groupColor"], |                 "color" => $row["groupColor"], | ||||||
| @ -296,6 +322,7 @@ namespace Api\User { | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       return $this->success; |       return $this->success; | ||||||
|     } |     } | ||||||
| @ -629,8 +656,8 @@ If the registration was not intended, you can simply ignore this email.<br><br>< | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Check for duplicate username, email
 |         // Check for duplicate username, email
 | ||||||
|         $usernameChanged = !is_null($username) ? strcasecmp($username, $this->user->getUsername()) !== 0 : false; |         $usernameChanged = !is_null($username) ? strcasecmp($username, $user[0]["name"]) !== 0 : false; | ||||||
|         $emailChanged = !is_null($email) ? strcasecmp($email, $this->user->getEmail()) !== 0 : false; |         $emailChanged = !is_null($email) ? strcasecmp($email, $user[0]["email"]) !== 0 : false; | ||||||
|         if($usernameChanged || $emailChanged) { |         if($usernameChanged || $emailChanged) { | ||||||
|           if (!$this->userExists($usernameChanged ? $username : NULL, $emailChanged ? $email : NULL)) { |           if (!$this->userExists($usernameChanged ? $username : NULL, $emailChanged ? $email : NULL)) { | ||||||
|             return false; |             return false; | ||||||
| @ -668,4 +695,41 @@ If the registration was not intended, you can simply ignore this email.<br><br>< | |||||||
|       return $this->success; |       return $this->success; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   class Delete extends UserAPI { | ||||||
|  | 
 | ||||||
|  |     public function __construct(User $user, bool $externalCall) { | ||||||
|  |       parent::__construct($user, $externalCall, array( | ||||||
|  |         'id' => new Parameter('id', Parameter::TYPE_INT) | ||||||
|  |       )); | ||||||
|  | 
 | ||||||
|  |       $this->requiredGroup = array(USER_GROUP_ADMIN); | ||||||
|  |       $this->loginRequired = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function execute($values = array()) { | ||||||
|  |       if (!parent::execute($values)) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       $id = $this->getParam("id"); | ||||||
|  |       if ($id === $this->user->getId()) { | ||||||
|  |         return $this->createError("You cannot delete your own user."); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       $user = $this->getUser($id); | ||||||
|  |       if ($this->success) { | ||||||
|  |         if (empty($user)) { | ||||||
|  |           return $this->createError("User not found"); | ||||||
|  |         } else { | ||||||
|  |           $sql = $this->user->getSQL(); | ||||||
|  |           $res = $sql->delete("User")->where(new Compare("uid", $id))->execute(); | ||||||
|  |           $this->success = ($res !== FALSE); | ||||||
|  |           $this->lastError = $sql->getLastError(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return $this->success; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
							
								
								
									
										19
									
								
								core/Driver/SQL/Condition/CondIn.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										19
									
								
								core/Driver/SQL/Condition/CondIn.class.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Driver\SQL\Condition; | ||||||
|  | 
 | ||||||
|  | use Driver\SQL\Column\Column; | ||||||
|  | 
 | ||||||
|  | class CondIn extends Condition { | ||||||
|  | 
 | ||||||
|  |   private string $column; | ||||||
|  |   private array $values; | ||||||
|  | 
 | ||||||
|  |   public function __construct(string $column, array $values) { | ||||||
|  |     $this->column = $column; | ||||||
|  |     $this->values = $values; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public function getColumn() { return $this->column; } | ||||||
|  |   public function getValues() { return $this->values; } | ||||||
|  | } | ||||||
| @ -5,6 +5,7 @@ namespace Driver\SQL; | |||||||
| use Driver\SQL\Column\Column; | use Driver\SQL\Column\Column; | ||||||
| use Driver\SQL\Condition\Compare; | use Driver\SQL\Condition\Compare; | ||||||
| use Driver\SQL\Condition\CondBool; | use Driver\SQL\Condition\CondBool; | ||||||
|  | use Driver\SQL\Condition\CondIn; | ||||||
| use Driver\SQL\Condition\CondOr; | use Driver\SQL\Condition\CondOr; | ||||||
| use Driver\SQL\Condition\Regex; | use Driver\SQL\Condition\Regex; | ||||||
| use Driver\SQL\Constraint\Constraint; | use Driver\SQL\Constraint\Constraint; | ||||||
| @ -334,6 +335,14 @@ abstract class SQL { | |||||||
|         } |         } | ||||||
|         return implode(" AND ", $conditions); |         return implode(" AND ", $conditions); | ||||||
|       } |       } | ||||||
|  |     } else if($condition instanceof CondIn) { | ||||||
|  |       $values = array(); | ||||||
|  |       foreach ($condition->getValues() as $value) { | ||||||
|  |         $values[] = $this->addValue($value, $params); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       $values = implode(",", $values); | ||||||
|  |       return $this->columnName($condition->getColumn()) . " IN ($values)"; | ||||||
|     } else { |     } else { | ||||||
|       $this->lastError = "Unsupported condition type: " . get_class($condition); |       $this->lastError = "Unsupported condition type: " . get_class($condition); | ||||||
|       return false; |       return false; | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								js/admin.min.js
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										8
									
								
								js/admin.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -51,6 +51,10 @@ export default class API { | |||||||
|         return this.apiCall("user/get", { id: id }); |         return this.apiCall("user/get", { id: id }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async deleteUser(id) { | ||||||
|  |         return this.apiCall("user/delete", { id: id }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async fetchUsers(pageNum = 1, count = 20) { |     async fetchUsers(pageNum = 1, count = 20) { | ||||||
|         return this.apiCall("user/fetch", { page: pageNum, count: count }); |         return this.apiCall("user/fetch", { page: pageNum, count: count }); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -6,6 +6,17 @@ export default function Dialog(props) { | |||||||
|     const classes = "modal fade" + (show ? " show" : ""); |     const classes = "modal fade" + (show ? " show" : ""); | ||||||
|     const style = { paddingRight: "12px", display: (show ? "block" : "none") }; |     const style = { paddingRight: "12px", display: (show ? "block" : "none") }; | ||||||
|     const onClose = props.onClose || function() { }; |     const onClose = props.onClose || function() { }; | ||||||
|  |     const onOption = props.onOption || function() { }; | ||||||
|  |     const options = props.options || ["Close"]; | ||||||
|  | 
 | ||||||
|  |     let buttons = []; | ||||||
|  |     for (let name of options) { | ||||||
|  |         buttons.push( | ||||||
|  |             <button type="button" key={"button-" + name} className="btn btn-default" data-dismiss={"modal"} onClick={() => { onClose(); onOption(name); }}> | ||||||
|  |                 {name} | ||||||
|  |             </button> | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <div className={classes} id="modal-default" style={style} aria-modal="true" onClick={() => onClose()}> |         <div className={classes} id="modal-default" style={style} aria-modal="true" onClick={() => onClose()}> | ||||||
| @ -20,8 +31,8 @@ export default function Dialog(props) { | |||||||
|                     <div className="modal-body"> |                     <div className="modal-body"> | ||||||
|                         <p>{props.message}</p> |                         <p>{props.message}</p> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div className="modal-footer justify-content-between"> |                     <div className="modal-footer"> | ||||||
|                         <button type="button" className="btn btn-default" data-dismiss="modal" onClick={() => onClose()}>Close</button> |                         { buttons } | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -34,8 +34,8 @@ class AdminDashboard extends React.Component { | |||||||
|     this.fetchNotifications(); |     this.fetchNotifications(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   showDialog(message, title) { |   showDialog(message, title, options=["Close"], onOption = null) { | ||||||
|     const props = { show: true, message: message, title: title }; |     const props = { show: true, message: message, title: title, options: options, onOption: onOption }; | ||||||
|     this.setState({ ...this.state, dialog: { ...this.state.dialog, ...props } }); |     this.setState({ ...this.state, dialog: { ...this.state.dialog, ...props } }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,7 +9,8 @@ export default class EditUser extends React.Component { | |||||||
|     constructor(props) { |     constructor(props) { | ||||||
|         super(props); |         super(props); | ||||||
|         this.parent = { |         this.parent = { | ||||||
|             api: props.api |             api: props.api, | ||||||
|  |             showDialog: props.showDialog, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         this.state = { |         this.state = { | ||||||
| @ -18,6 +19,7 @@ export default class EditUser extends React.Component { | |||||||
|             fetchError: null, |             fetchError: null, | ||||||
|             loaded: false, |             loaded: false, | ||||||
|             isSaving: false, |             isSaving: false, | ||||||
|  |             isDeleting: false, | ||||||
|             groups: { }, |             groups: { }, | ||||||
|             searchString: "", |             searchString: "", | ||||||
|             searchActive: false |             searchActive: false | ||||||
| @ -37,7 +39,13 @@ export default class EditUser extends React.Component { | |||||||
|     componentDidMount() { |     componentDidMount() { | ||||||
|         this.parent.api.getUser(this.props.match.params["userId"]).then((res) => { |         this.parent.api.getUser(this.props.match.params["userId"]).then((res) => { | ||||||
|             if (res.success) { |             if (res.success) { | ||||||
|                 this.setState({ ...this.state, user: {... res.user, password: ""} }); |                 this.setState({ ...this.state, user: { | ||||||
|  |                         name: res.user.name, | ||||||
|  |                         email: res.user.email || "", | ||||||
|  |                         groups: res.user.groups, | ||||||
|  |                         password: "" | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|                 this.parent.api.fetchGroups(1, 50).then((res) => { |                 this.parent.api.fetchGroups(1, 50).then((res) => { | ||||||
|                     if (res.success) { |                     if (res.success) { | ||||||
|                         this.setState({ ...this.state, groups: res.groups, loaded: true }); |                         this.setState({ ...this.state, groups: res.groups, loaded: true }); | ||||||
| @ -92,6 +100,27 @@ export default class EditUser extends React.Component { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     onDeleteUser(event) { | ||||||
|  |         event.preventDefault(); | ||||||
|  |         event.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         const id = this.props.match.params["userId"]; | ||||||
|  | 
 | ||||||
|  |         this.parent.showDialog("Are you sure you want to delete this user permanently?", "Delete User?", ["Yes", "No"], (btn) => { | ||||||
|  |             if (btn === "Yes") { | ||||||
|  |                 this.parent.api.deleteUser(id).then((res) => { | ||||||
|  |                     if (res.success) { | ||||||
|  |                         this.props.history.push("/admin/users"); | ||||||
|  |                     } else { | ||||||
|  |                         let alerts = this.state.alerts.slice(); | ||||||
|  |                         alerts.push({ title: "Error deleting user", message: res.msg, type: "danger" }); | ||||||
|  |                         this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } }); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     onRemoveGroup(event, groupId) { |     onRemoveGroup(event, groupId) { | ||||||
|         event.stopPropagation(); |         event.stopPropagation(); | ||||||
|         if (this.state.user.groups.hasOwnProperty(groupId)) { |         if (this.state.user.groups.hasOwnProperty(groupId)) { | ||||||
| @ -178,7 +207,7 @@ export default class EditUser extends React.Component { | |||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             form = <form role={"form"} onSubmit={this.onSubmitForm.bind(this)}> |             form = <form role={"form"} onSubmit={(e) => e.preventDefault()}> | ||||||
|                 <div className={"form-group"}> |                 <div className={"form-group"}> | ||||||
|                     <label htmlFor={"username"}>Username</label> |                     <label htmlFor={"username"}>Username</label> | ||||||
|                     <input type={"text"} className={"form-control"} placeholder={"Enter username"} |                     <input type={"text"} className={"form-control"} placeholder={"Enter username"} | ||||||
| @ -238,8 +267,12 @@ export default class EditUser extends React.Component { | |||||||
|                      Back |                      Back | ||||||
|                 </Link> |                 </Link> | ||||||
|                 { this.state.isSaving |                 { this.state.isSaving | ||||||
|                     ? <button type={"submit"} className={"btn btn-primary mt-2"} disabled>Saving… <Icon icon={"circle-notch"} /></button> |                     ? <button type={"submit"} className={"btn btn-primary mt-2 mr-2"} disabled>Saving… <Icon icon={"circle-notch"} /></button> | ||||||
|                     : <button type={"submit"} className={"btn btn-primary mt-2"}>Save</button> |                     : <button type={"submit"} className={"btn btn-primary mt-2 mr-2"} onClick={this.onSubmitForm.bind(this)}>Save</button> | ||||||
|  |                 } | ||||||
|  |                 { this.state.isDeleting | ||||||
|  |                     ? <button type={"submit"} className={"btn btn-danger mt-2"} disabled>Deleting… <Icon icon={"circle-notch"} /></button> | ||||||
|  |                     : <button type={"submit"} className={"btn btn-danger mt-2"} onClick={this.onDeleteUser.bind(this)}>Delete</button> | ||||||
|                 } |                 } | ||||||
|             </form> |             </form> | ||||||
|         } |         } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user