Bug Fixes + IN Operator + User deletion

This commit is contained in:
Roman Hergenreder 2020-06-23 22:24:56 +02:00
parent e6361a3c91
commit d8846ff132
8 changed files with 163 additions and 23 deletions

@ -110,6 +110,7 @@ namespace Api\User {
use Api\UserAPI;
use DateTime;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondIn;
use Objects\User;
class Create extends UserAPI {
@ -190,6 +191,28 @@ namespace Api\User {
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()) {
if (!parent::execute($values)) {
return false;
@ -209,16 +232,18 @@ namespace Api\User {
return false;
}
$userIds = $this->selectIds($page, $count);
if ($userIds === false) {
return false;
}
$sql = $this->user->getSQL();
$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")
->from("User")
->leftJoin("UserGroup", "User.uid", "UserGroup.user_id")
->leftJoin("Group", "Group.uid", "UserGroup.group_id")
->orderBy("User.uid")
->ascending()
->limit($count)
->offset(($page - 1) * $count)
->where(new CondIn("User.uid", $userIds))
->execute();
$this->success = ($res !== FALSE);
@ -289,6 +314,7 @@ namespace Api\User {
);
foreach($user as $row) {
if (!is_null($row["groupId"])) {
$this->result["user"]["groups"][$row["groupId"]] = array(
"name" => $row["groupName"],
"color" => $row["groupColor"],
@ -296,6 +322,7 @@ namespace Api\User {
}
}
}
}
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
$usernameChanged = !is_null($username) ? strcasecmp($username, $this->user->getUsername()) !== 0 : false;
$emailChanged = !is_null($email) ? strcasecmp($email, $this->user->getEmail()) !== 0 : false;
$usernameChanged = !is_null($username) ? strcasecmp($username, $user[0]["name"]) !== 0 : false;
$emailChanged = !is_null($email) ? strcasecmp($email, $user[0]["email"]) !== 0 : false;
if($usernameChanged || $emailChanged) {
if (!$this->userExists($usernameChanged ? $username : NULL, $emailChanged ? $email : NULL)) {
return false;
@ -668,4 +695,41 @@ If the registration was not intended, you can simply ignore this email.<br><br><
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;
}
}
}

@ -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\Condition\Compare;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\CondIn;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Condition\Regex;
use Driver\SQL\Constraint\Constraint;
@ -334,6 +335,14 @@ abstract class SQL {
}
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 {
$this->lastError = "Unsupported condition type: " . get_class($condition);
return false;

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 });
}
async deleteUser(id) {
return this.apiCall("user/delete", { id: id });
}
async fetchUsers(pageNum = 1, count = 20) {
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 style = { paddingRight: "12px", display: (show ? "block" : "none") };
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 (
<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">
<p>{props.message}</p>
</div>
<div className="modal-footer justify-content-between">
<button type="button" className="btn btn-default" data-dismiss="modal" onClick={() => onClose()}>Close</button>
<div className="modal-footer">
{ buttons }
</div>
</div>
</div>

@ -34,8 +34,8 @@ class AdminDashboard extends React.Component {
this.fetchNotifications();
}
showDialog(message, title) {
const props = { show: true, message: message, title: title };
showDialog(message, title, options=["Close"], onOption = null) {
const props = { show: true, message: message, title: title, options: options, onOption: onOption };
this.setState({ ...this.state, dialog: { ...this.state.dialog, ...props } });
}

@ -9,7 +9,8 @@ export default class EditUser extends React.Component {
constructor(props) {
super(props);
this.parent = {
api: props.api
api: props.api,
showDialog: props.showDialog,
};
this.state = {
@ -18,6 +19,7 @@ export default class EditUser extends React.Component {
fetchError: null,
loaded: false,
isSaving: false,
isDeleting: false,
groups: { },
searchString: "",
searchActive: false
@ -37,7 +39,13 @@ export default class EditUser extends React.Component {
componentDidMount() {
this.parent.api.getUser(this.props.match.params["userId"]).then((res) => {
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) => {
if (res.success) {
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) {
event.stopPropagation();
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"}>
<label htmlFor={"username"}>Username</label>
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
@ -238,8 +267,12 @@ export default class EditUser extends React.Component {
&nbsp;Back
</Link>
{ this.state.isSaving
? <button type={"submit"} className={"btn btn-primary mt-2"} disabled>Saving&nbsp;<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"} disabled>Saving&nbsp;<Icon icon={"circle-notch"} /></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&nbsp;<Icon icon={"circle-notch"} /></button>
: <button type={"submit"} className={"btn btn-danger mt-2"} onClick={this.onDeleteUser.bind(this)}>Delete</button>
}
</form>
}