Skip to content

Commit c4e8be3

Browse files
committed
Thêm function quản lý người dùng của admin
1 parent e32d98c commit c4e8be3

13 files changed

Lines changed: 372 additions & 46 deletions

File tree

client/src/main/java/com/auction/controller/AuctionListController.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public class AuctionListController {
5252
@FXML private Button deleteBtn;
5353
@FXML private Button placeBidBtn;
5454
@FXML private Button paySellerBtn;
55+
@FXML private TabPane mainTabs;
56+
@FXML private Tab manageUsersTab;
57+
@FXML private TableView<User> userTable;
58+
@FXML private TableColumn<User, String> colUserId;
59+
@FXML private TableColumn<User, String> colUsername;
60+
@FXML private TableColumn<User, String> colUserRole;
61+
@FXML private TableColumn<User, String> colUserBalance;
62+
@FXML private TableColumn<User, String> colUserBanned;
5563
@FXML private Label statusLabel;
5664

5765
private AppState appState;
@@ -62,8 +70,10 @@ public void init(AppState appState, Stage stage) {
6270
this.appState = appState;
6371
this.stage = stage;
6472
setupTable();
73+
setupUserTable();
6574
configureRoleButtons();
6675
refreshTable();
76+
refreshUsers();
6777
stage.setTitle("Auction Platform – " + appState.currentUser.getUsername());
6878

6979
appState.auctionService.setStatusChangeCallback(() -> Platform.runLater(this::refreshTable));
@@ -118,12 +128,22 @@ private void configureRoleButtons() {
118128
show(editItemBtn, startBtn, endEarlyBtn, cancelBtn, deleteBtn);
119129
} else if (appState.currentUser instanceof Seller) {
120130
show(addFundsBtn, withdrawBtn, createItemBtn, editItemBtn, deleteBtn, startBtn, placeBidBtn);
131+
mainTabs.getTabs().remove(manageUsersTab);
121132
} else if (appState.currentUser instanceof Bidder) {
122133
show(addFundsBtn, withdrawBtn, placeBidBtn);
134+
mainTabs.getTabs().remove(manageUsersTab);
123135
}
124136
updatePaySellerButton();
125137
}
126138

139+
private void setupUserTable() {
140+
colUserId.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().getId()));
141+
colUsername.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().getUsername()));
142+
colUserRole.setCellValueFactory(c -> new SimpleStringProperty(roleOf(c.getValue())));
143+
colUserBalance.setCellValueFactory(c -> new SimpleStringProperty(balanceOf(c.getValue())));
144+
colUserBanned.setCellValueFactory(c -> new SimpleStringProperty(bannedText(c.getValue())));
145+
}
146+
127147
private void show(javafx.scene.Node... nodes) {
128148
for (javafx.scene.Node n : nodes) { n.setVisible(true); n.setManaged(true); }
129149
}
@@ -289,6 +309,122 @@ private void refreshTable() {
289309
updatePaySellerButton();
290310
}
291311

312+
@FXML
313+
private void onRefreshUsers() {
314+
refreshUsers();
315+
showStatus("User list refreshed.", false);
316+
}
317+
318+
@FXML
319+
private void onBanUser() {
320+
withSelectedUser(user -> {
321+
appState.restUserService.banUser(user.getId());
322+
refreshUsers();
323+
showStatus("User banned.", false);
324+
});
325+
}
326+
327+
@FXML
328+
private void onChangeUsername() {
329+
withSelectedUser(user -> {
330+
TextInputDialog dlg = new TextInputDialog(user.getUsername());
331+
dlg.setTitle("Change Username");
332+
dlg.setHeaderText("Enter a new username:");
333+
dlg.showAndWait().ifPresent(username -> {
334+
try {
335+
User updated = appState.restUserService.changeUsername(user.getId(), username.trim());
336+
if (appState.currentUser.getId().equals(updated.getId())) {
337+
appState.currentUser = updated;
338+
refreshUserInfo();
339+
}
340+
refreshUsers();
341+
showStatus("Username updated.", false);
342+
} catch (Exception e) {
343+
showStatus(e.getMessage(), true);
344+
}
345+
});
346+
});
347+
}
348+
349+
@FXML
350+
private void onChangePassword() {
351+
withSelectedUser(user -> {
352+
Dialog<String> dlg = new Dialog<>();
353+
dlg.setTitle("Change Password");
354+
dlg.setHeaderText("Enter a new password:");
355+
PasswordField passwordField = new PasswordField();
356+
passwordField.setPromptText("New password");
357+
dlg.getDialogPane().setContent(passwordField);
358+
ButtonType saveBtn = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
359+
dlg.getDialogPane().getButtonTypes().addAll(saveBtn, ButtonType.CANCEL);
360+
dlg.setResultConverter(btn -> btn == saveBtn ? passwordField.getText() : null);
361+
dlg.showAndWait().ifPresent(password -> {
362+
try {
363+
appState.restUserService.changePassword(user.getId(), password);
364+
refreshUsers();
365+
showStatus("Password updated.", false);
366+
} catch (Exception e) {
367+
showStatus(e.getMessage(), true);
368+
}
369+
});
370+
});
371+
}
372+
373+
@FXML
374+
private void onDeleteUser() {
375+
withSelectedUser(user -> {
376+
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
377+
confirm.setTitle("Delete Account");
378+
confirm.setHeaderText("Delete account \"" + user.getUsername() + "\"?");
379+
confirm.setContentText("This action cannot be undone.");
380+
confirm.showAndWait()
381+
.filter(btn -> btn == ButtonType.OK)
382+
.ifPresent(btn -> {
383+
try {
384+
appState.restUserService.deleteUser(user.getId());
385+
refreshUsers();
386+
showStatus("User deleted.", false);
387+
} catch (Exception e) {
388+
showStatus(e.getMessage(), true);
389+
}
390+
});
391+
});
392+
}
393+
394+
private void refreshUsers() {
395+
if (!(appState.currentUser instanceof Admin) || userTable == null) return;
396+
try {
397+
userTable.setItems(FXCollections.observableArrayList(appState.restUserService.findAllUsers()));
398+
userTable.refresh();
399+
} catch (Exception e) {
400+
showStatus(e.getMessage(), true);
401+
}
402+
}
403+
404+
private void withSelectedUser(java.util.function.Consumer<User> action) {
405+
User selected = userTable.getSelectionModel().getSelectedItem();
406+
if (selected == null) { showStatus("Select a user first.", true); return; }
407+
try { action.accept(selected); }
408+
catch (Exception e) { showStatus(e.getMessage(), true); }
409+
}
410+
411+
private String roleOf(User user) {
412+
if (user instanceof Admin) return "ADMIN";
413+
if (user instanceof Seller) return "SELLER";
414+
return "BIDDER";
415+
}
416+
417+
private String balanceOf(User user) {
418+
if (user instanceof Bidder b) return "$" + String.format("%.2f", b.getBalance());
419+
if (user instanceof Seller s) return "$" + String.format("%.2f", s.getBalance());
420+
return "—";
421+
}
422+
423+
private String bannedText(User user) {
424+
if (user instanceof BannableUser bu) return bu.isBanned() ? "Yes" : "No";
425+
return "N/A";
426+
}
427+
292428
private void updatePaySellerButton() {
293429
if (paySellerBtn == null || appState == null || appState.currentUser == null) return;
294430
Item selected = itemTable.getSelectionModel().getSelectedItem();

client/src/main/java/com/auction/repository/rest/RestUserRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,10 @@ public List<User> findAll() {
7272
return List.of();
7373
}
7474
}
75+
76+
@Override
77+
public void delete(String id) {
78+
throw new UnsupportedOperationException(
79+
"RestUserRepository is read-only. Use UserService for admin account changes.");
80+
}
7581
}

client/src/main/java/com/auction/service/rest/RestUserService.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.io.IOException;
1313
import java.lang.reflect.Type;
14+
import java.util.List;
1415
import java.util.LinkedHashMap;
1516
import java.util.Map;
1617

@@ -107,6 +108,59 @@ public User refresh(String userId) {
107108
}
108109
}
109110

111+
public List<User> findAllUsers() {
112+
try {
113+
Type listType = new TypeToken<List<Map<String, Object>>>(){}.getType();
114+
String response = http.get("/users");
115+
List<Map<String, Object>> users = http.getGson().fromJson(response, listType);
116+
return users.stream().map(JsonMappers::toUser).toList();
117+
} catch (IOException e) {
118+
throw new UnauthorizedActionException(extract(e));
119+
}
120+
}
121+
122+
public User banUser(String userId) {
123+
try {
124+
String response = http.post("/users/" + userId + "/ban", "{}");
125+
Map<String, Object> map = http.getGson().fromJson(response, MAP);
126+
return JsonMappers.toUser(map);
127+
} catch (IOException e) {
128+
throw new UnauthorizedActionException(extract(e));
129+
}
130+
}
131+
132+
public User changeUsername(String userId, String username) {
133+
try {
134+
Map<String, String> body = new LinkedHashMap<>();
135+
body.put("username", username);
136+
String response = http.put("/users/" + userId + "/username", http.getGson().toJson(body));
137+
Map<String, Object> map = http.getGson().fromJson(response, MAP);
138+
return JsonMappers.toUser(map);
139+
} catch (IOException e) {
140+
throw new IllegalArgumentException(extract(e));
141+
}
142+
}
143+
144+
public User changePassword(String userId, String password) {
145+
try {
146+
Map<String, String> body = new LinkedHashMap<>();
147+
body.put("password", password);
148+
String response = http.put("/users/" + userId + "/password", http.getGson().toJson(body));
149+
Map<String, Object> map = http.getGson().fromJson(response, MAP);
150+
return JsonMappers.toUser(map);
151+
} catch (IOException e) {
152+
throw new IllegalArgumentException(extract(e));
153+
}
154+
}
155+
156+
public void deleteUser(String userId) {
157+
try {
158+
http.delete("/users/" + userId);
159+
} catch (IOException e) {
160+
throw new UnauthorizedActionException(extract(e));
161+
}
162+
}
163+
110164
private static String extract(IOException e) {
111165
String msg = e.getMessage();
112166
return msg == null ? "Server error" : msg;

client/src/main/resources/fxml/auction-list.fxml

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,62 @@
1616
<Button text="Logout" onAction="#onLogout" styleClass="btn-danger"/>
1717
</HBox>
1818

19-
<!-- Table -->
20-
<TableView fx:id="itemTable" VBox.vgrow="ALWAYS" styleClass="item-table">
21-
<columns>
22-
<TableColumn fx:id="colName" text="Name" prefWidth="160"/>
23-
<TableColumn fx:id="colType" text="Type" prefWidth="90"/>
24-
<TableColumn fx:id="colStatus" text="Status" prefWidth="90"/>
25-
<TableColumn fx:id="colPrice" text="Current Price" prefWidth="110"/>
26-
<TableColumn fx:id="colMinBid" text="Min Next Bid" prefWidth="110"/>
27-
<TableColumn fx:id="colEndTime" text="End Time" prefWidth="150"/>
28-
<TableColumn fx:id="colWinner" text="Winner" prefWidth="110"/>
29-
</columns>
30-
<columnResizePolicy>
31-
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
32-
</columnResizePolicy>
33-
</TableView>
19+
<TabPane fx:id="mainTabs" VBox.vgrow="ALWAYS" tabClosingPolicy="UNAVAILABLE">
20+
<Tab text="Auctions">
21+
<VBox spacing="10">
22+
<TableView fx:id="itemTable" VBox.vgrow="ALWAYS" styleClass="item-table">
23+
<columns>
24+
<TableColumn fx:id="colName" text="Name" prefWidth="160"/>
25+
<TableColumn fx:id="colType" text="Type" prefWidth="90"/>
26+
<TableColumn fx:id="colStatus" text="Status" prefWidth="90"/>
27+
<TableColumn fx:id="colPrice" text="Current Price" prefWidth="110"/>
28+
<TableColumn fx:id="colMinBid" text="Min Next Bid" prefWidth="110"/>
29+
<TableColumn fx:id="colEndTime" text="End Time" prefWidth="150"/>
30+
<TableColumn fx:id="colWinner" text="Winner" prefWidth="110"/>
31+
</columns>
32+
<columnResizePolicy>
33+
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
34+
</columnResizePolicy>
35+
</TableView>
3436

35-
<!-- Action bar -->
36-
<HBox spacing="8" alignment="CENTER_LEFT">
37-
<!-- Seller -->
38-
<Button fx:id="createItemBtn" text="Create Item" onAction="#onCreateItem"
39-
styleClass="btn-primary" managed="false" visible="false"/>
40-
<Button fx:id="editItemBtn" text="Edit Item" onAction="#onEditItem"
41-
styleClass="btn-secondary" managed="false" visible="false"/>
42-
43-
<!-- Admin -->
44-
<Button fx:id="startBtn" text="Start" onAction="#onStart" styleClass="btn-primary" managed="false" visible="false"/>
45-
<Button fx:id="endEarlyBtn" text="End Early" onAction="#onEndEarly" styleClass="btn-secondary" managed="false" visible="false"/>
46-
<Button fx:id="cancelBtn" text="Cancel" onAction="#onCancel" styleClass="btn-danger" managed="false" visible="false"/>
47-
<Button fx:id="deleteBtn" text="Delete" onAction="#onDelete" styleClass="btn-danger" managed="false" visible="false"/>
48-
49-
<!-- Bidder / Seller -->
50-
<Button fx:id="placeBidBtn" text="Place Bid" onAction="#onPlaceBid" styleClass="btn-primary" managed="false" visible="false"/>
51-
<Button fx:id="paySellerBtn" text="Pay Seller" onAction="#onPaySeller" styleClass="btn-primary" managed="false" visible="false"/>
52-
</HBox>
37+
<HBox spacing="8" alignment="CENTER_LEFT">
38+
<Button fx:id="createItemBtn" text="Create Item" onAction="#onCreateItem"
39+
styleClass="btn-primary" managed="false" visible="false"/>
40+
<Button fx:id="editItemBtn" text="Edit Item" onAction="#onEditItem"
41+
styleClass="btn-secondary" managed="false" visible="false"/>
42+
<Button fx:id="startBtn" text="Start" onAction="#onStart" styleClass="btn-primary" managed="false" visible="false"/>
43+
<Button fx:id="endEarlyBtn" text="End Early" onAction="#onEndEarly" styleClass="btn-secondary" managed="false" visible="false"/>
44+
<Button fx:id="cancelBtn" text="Cancel" onAction="#onCancel" styleClass="btn-danger" managed="false" visible="false"/>
45+
<Button fx:id="deleteBtn" text="Delete" onAction="#onDelete" styleClass="btn-danger" managed="false" visible="false"/>
46+
<Button fx:id="placeBidBtn" text="Place Bid" onAction="#onPlaceBid" styleClass="btn-primary" managed="false" visible="false"/>
47+
<Button fx:id="paySellerBtn" text="Pay Seller" onAction="#onPaySeller" styleClass="btn-primary" managed="false" visible="false"/>
48+
</HBox>
49+
</VBox>
50+
</Tab>
51+
<Tab fx:id="manageUsersTab" text="Manage users">
52+
<VBox spacing="10">
53+
<TableView fx:id="userTable" VBox.vgrow="ALWAYS" styleClass="item-table">
54+
<columns>
55+
<TableColumn fx:id="colUserId" text="ID" prefWidth="210"/>
56+
<TableColumn fx:id="colUsername" text="Username" prefWidth="150"/>
57+
<TableColumn fx:id="colUserRole" text="Role" prefWidth="90"/>
58+
<TableColumn fx:id="colUserBalance" text="Balance" prefWidth="110"/>
59+
<TableColumn fx:id="colUserBanned" text="Banned" prefWidth="90"/>
60+
</columns>
61+
<columnResizePolicy>
62+
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
63+
</columnResizePolicy>
64+
</TableView>
65+
<HBox spacing="8" alignment="CENTER_LEFT">
66+
<Button text="Refresh" onAction="#onRefreshUsers" styleClass="btn-secondary"/>
67+
<Button text="Ban" onAction="#onBanUser" styleClass="btn-danger"/>
68+
<Button text="Change Username" onAction="#onChangeUsername" styleClass="btn-secondary"/>
69+
<Button text="Change Password" onAction="#onChangePassword" styleClass="btn-secondary"/>
70+
<Button text="Delete Account" onAction="#onDeleteUser" styleClass="btn-danger"/>
71+
</HBox>
72+
</VBox>
73+
</Tab>
74+
</TabPane>
5375

5476
<Label fx:id="statusLabel" styleClass="error-label"/>
5577
</VBox>

server/src/main/java/com/auction/repository/SqliteUserRepository.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ public List<User> findAll() {
8383
}
8484
}
8585

86+
@Override
87+
public void delete(String id) {
88+
try (PreparedStatement ps = conn.prepareStatement("DELETE FROM users WHERE id = ?")) {
89+
ps.setString(1, id);
90+
ps.executeUpdate();
91+
} catch (SQLException e) {
92+
throw new RuntimeException("Failed to delete user: " + e.getMessage(), e);
93+
}
94+
}
95+
8696
private User mapRow(ResultSet rs) throws SQLException {
8797
String id = rs.getString("id");
8898
String username = rs.getString("username");

0 commit comments

Comments
 (0)