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,10 +314,12 @@ namespace Api\User {
|
|||||||
);
|
);
|
||||||
|
|
||||||
foreach($user as $row) {
|
foreach($user as $row) {
|
||||||
$this->result["user"]["groups"][$row["groupId"]] = array(
|
if (!is_null($row["groupId"])) {
|
||||||
"name" => $row["groupName"],
|
$this->result["user"]["groups"][$row["groupId"]] = array(
|
||||||
"color" => $row["groupColor"],
|
"name" => $row["groupName"],
|
||||||
);
|
"color" => $row["groupColor"],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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