Clear DMs history button
This commit is contained in:
parent
d510ac791f
commit
3424be2f4d
33
docs/API.md
33
docs/API.md
|
@ -277,4 +277,35 @@ The response JSON given to the chat page from /api/profile looks like:
|
|||
|
||||
The "Remaining" integer in the result shows how many older messages still
|
||||
remain to be retrieved, and tells the front-end page that it can request
|
||||
another page.
|
||||
another page.
|
||||
|
||||
## POST /api/message/clear
|
||||
|
||||
Clear stored direct message history for a user.
|
||||
|
||||
This endpoint can be called by the user themself (using JWT token authorization),
|
||||
or by your website (using your admin APIKey) so your site can also clear chat
|
||||
history remotely (e.g., for when your user deleted their account).
|
||||
|
||||
The request body payload looks like:
|
||||
|
||||
```javascript
|
||||
{
|
||||
// when called from the BareRTC frontend for the current user
|
||||
"JWTToken": "the caller's chat jwt token",
|
||||
|
||||
// when called from your website
|
||||
"APIKey": "your AdminAPIKey from settings.toml",
|
||||
"Username": "soandso"
|
||||
}
|
||||
```
|
||||
|
||||
The response JSON given to the chat page looks like:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"OK": true,
|
||||
"Error": "only on error messages",
|
||||
"MessagesErased": 42
|
||||
}
|
||||
```
|
||||
|
|
122
pkg/api.go
122
pkg/api.go
|
@ -881,6 +881,128 @@ func (s *Server) MessageHistory() http.HandlerFunc {
|
|||
})
|
||||
}
|
||||
|
||||
// ClearMessages (/api/message/clear) deletes all the stored direct messages for a user.
|
||||
//
|
||||
// It can be called by the authenticated user themself (with JWTToken), or from your website
|
||||
// (with APIKey) in which case you can remotely clear history for a user.
|
||||
//
|
||||
// It is a POST request with a json body containing the following schema:
|
||||
//
|
||||
// {
|
||||
// "JWTToken": "the caller's jwt token",
|
||||
// "APIKey": "your website's admin API key"
|
||||
// "Username": "if using your APIKey to specify a user to delete",
|
||||
// }
|
||||
//
|
||||
// The response JSON will look like the following:
|
||||
//
|
||||
// {
|
||||
// "OK": true,
|
||||
// "Error": "only on error responses",
|
||||
// "MessagesErased": 123,
|
||||
// }
|
||||
//
|
||||
// The Remaining value is how many older messages still exist to be loaded.
|
||||
func (s *Server) ClearMessages() http.HandlerFunc {
|
||||
type request struct {
|
||||
JWTToken string
|
||||
APIKey string
|
||||
Username string
|
||||
}
|
||||
|
||||
type result struct {
|
||||
OK bool
|
||||
Error string `json:",omitempty"`
|
||||
MessagesErased int `json:""`
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// JSON writer for the response.
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
// Parse the request.
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
enc.Encode(result{
|
||||
Error: "Only POST methods allowed",
|
||||
})
|
||||
return
|
||||
} else if r.Header.Get("Content-Type") != "application/json" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
enc.Encode(result{
|
||||
Error: "Only application/json content-types allowed",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
// Parse the request payload.
|
||||
var (
|
||||
params request
|
||||
dec = json.NewDecoder(r.Body)
|
||||
)
|
||||
if err := dec.Decode(¶ms); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
enc.Encode(result{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Authenticate this request.
|
||||
if params.APIKey != "" {
|
||||
// By admin API key.
|
||||
if params.APIKey != config.Current.AdminAPIKey {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
enc.Encode(result{
|
||||
Error: "Authentication denied.",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Are JWT tokens enabled on the server?
|
||||
if !config.Current.JWT.Enabled || params.JWTToken == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
enc.Encode(result{
|
||||
Error: "JWT authentication is not available.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the user's JWT token.
|
||||
claims, _, err := jwt.ParseAndValidate(params.JWTToken)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
enc.Encode(result{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set the username to clear.
|
||||
params.Username = claims.Subject
|
||||
}
|
||||
|
||||
// Erase their message history.
|
||||
count, err := (models.DirectMessage{}).ClearMessages(params.Username)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
enc.Encode(result{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
enc.Encode(result{
|
||||
OK: true,
|
||||
MessagesErased: count,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Blocklist cache sent over from your website.
|
||||
var (
|
||||
// Map of username to the list of usernames they block.
|
||||
|
|
|
@ -82,6 +82,42 @@ func (dm DirectMessage) LogMessage(fromUsername, toUsername string, msg messages
|
|||
return err
|
||||
}
|
||||
|
||||
// ClearMessages clears all stored DMs that the username as a participant in.
|
||||
func (dm DirectMessage) ClearMessages(username string) (int, error) {
|
||||
if DB == nil {
|
||||
return 0, ErrNotInitialized
|
||||
}
|
||||
|
||||
var placeholders = []interface{}{
|
||||
fmt.Sprintf("@%s:%%", username), // `@alice:%`
|
||||
fmt.Sprintf("%%:@%s", username), // `%:@alice`
|
||||
username,
|
||||
}
|
||||
|
||||
// Count all the messages we'll delete.
|
||||
var (
|
||||
count int
|
||||
row = DB.QueryRow(`
|
||||
SELECT COUNT(message_id)
|
||||
FROM direct_messages
|
||||
WHERE (channel_id LIKE ? OR channel_id LIKE ?)
|
||||
OR username = ?
|
||||
`, placeholders...)
|
||||
)
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Delete them all.
|
||||
_, err := DB.Exec(`
|
||||
DELETE FROM direct_messages
|
||||
WHERE (channel_id LIKE ? OR channel_id LIKE ?)
|
||||
OR username = ?
|
||||
`, placeholders...)
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// TakebackMessage removes a message by its MID from the DM history.
|
||||
//
|
||||
// Because the MessageID may have been from a previous chat session, the server can't immediately
|
||||
|
|
|
@ -58,6 +58,7 @@ func (s *Server) Setup() error {
|
|||
mux.Handle("/api/shutdown", s.ShutdownAPI())
|
||||
mux.Handle("/api/profile", s.UserProfile())
|
||||
mux.Handle("/api/message/history", s.MessageHistory())
|
||||
mux.Handle("/api/message/clear", s.ClearMessages())
|
||||
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("dist/assets"))))
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("dist/static"))))
|
||||
|
||||
|
|
104
src/App.vue
104
src/App.vue
|
@ -282,6 +282,12 @@ export default {
|
|||
}
|
||||
*/
|
||||
},
|
||||
clearDirectMessages: {
|
||||
busy: false,
|
||||
ok: false,
|
||||
messagesErased: 0,
|
||||
timeout: null,
|
||||
},
|
||||
|
||||
// Responsive CSS controls for mobile.
|
||||
responsive: {
|
||||
|
@ -1037,6 +1043,13 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
// Clear user chat history.
|
||||
if (this.message.toLowerCase().indexOf("/clear-history") === 0) {
|
||||
this.clearMessageHistory();
|
||||
this.message = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// console.debug("Send message: %s", this.message);
|
||||
this.client.send({
|
||||
action: "message",
|
||||
|
@ -3277,6 +3290,65 @@ export default {
|
|||
this.directMessageHistory[channel].busy = false;
|
||||
});
|
||||
},
|
||||
async clearMessageHistory(prompt = false) {
|
||||
if (!this.jwt.valid || this.clearDirectMessages.busy) return;
|
||||
|
||||
if (prompt) {
|
||||
if (!window.confirm(
|
||||
"This will delete all of your DMs history stored on the server. People you have " +
|
||||
"chatted with will have their past messages sent to you erased as well.\n\n" +
|
||||
"Note: messages that are currently displayed on your chat partner's screen will " +
|
||||
"NOT be removed by this action -- if this is a concern and you want to 'take back' " +
|
||||
"a message from their screen, use the 'take back' button (red arrow circle) on the " +
|
||||
"message you sent to them. The 'clear history' button only clears the database, but " +
|
||||
"does not send takebacks to pull the message from everybody else's screen.\n\n" +
|
||||
"Are you sure you want to clear your stored DMs history on the server?",
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.clearDirectMessages.timeout !== null) {
|
||||
clearTimeout(this.clearDirectMessages.timeout);
|
||||
}
|
||||
|
||||
this.clearDirectMessages.busy = true;
|
||||
return fetch("/api/message/clear", {
|
||||
method: "POST",
|
||||
mode: "same-origin",
|
||||
cache: "no-cache",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"JWTToken": this.jwt.token,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.Error) {
|
||||
console.error("ClearMessageHistory: ", data.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearDirectMessages.ok = true;
|
||||
this.clearDirectMessages.messagesErased = data.MessagesErased;
|
||||
this.clearDirectMessages.timeout = setTimeout(() => {
|
||||
this.clearDirectMessages.ok = false;
|
||||
}, 15000);
|
||||
|
||||
this.ChatClient(
|
||||
"Your direct message history has been cleared from the server's database. "+
|
||||
"(" + data.MessagesErased + " messages erased)",
|
||||
);
|
||||
}).catch(resp => {
|
||||
console.error("DirectMessageHistory: ", resp);
|
||||
this.ChatClient("Error clearing your chat history: " + resp);
|
||||
}).finally(() => {
|
||||
this.clearDirectMessages.busy = false;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Webhook methods
|
||||
|
@ -3387,6 +3459,11 @@ export default {
|
|||
Misc
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ 'is-active': settingsModal.tab === 'advanced' }">
|
||||
<a href="#" @click.prevent="settingsModal.tab = 'advanced'">
|
||||
Advanced
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -3760,7 +3837,7 @@ export default {
|
|||
|
||||
<div class="field">
|
||||
<label class="label mb-0">Direct Messages</label>
|
||||
<label class="checkbox">
|
||||
<label class="checkbox mb-0">
|
||||
<input type="checkbox" v-model="prefs.closeDMs" :value="true">
|
||||
Ignore unsolicited DMs from others
|
||||
</label>
|
||||
|
@ -3771,6 +3848,27 @@ export default {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Clear DMs history on server -->
|
||||
<div class="field" v-if="this.jwt.valid">
|
||||
<a href="#" @click.prevent="clearMessageHistory(true)" class="button is-small has-text-danger">
|
||||
<i class="fa fa-trash mr-1"></i> Clear direct message history
|
||||
</a>
|
||||
|
||||
<div v-if="clearDirectMessages.busy" class="has-text-success mt-2 is-size-7">
|
||||
<i class="fa fa-spinner fa-spin mr-1"></i>
|
||||
Working...
|
||||
</div>
|
||||
<div v-else-if="clearDirectMessages.ok" class="has-text-success mt-2 is-size-7">
|
||||
<i class="fa fa-check mr-1"></i>
|
||||
History cleared ({{ clearDirectMessages.messagesErased }} message{{ clearDirectMessages.messagesErased === 1 ? '' : 's' }} erased)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Advanced preferences -->
|
||||
<div v-if="settingsModal.tab === 'advanced'">
|
||||
|
||||
<div class="field">
|
||||
<label class="label mb-0">
|
||||
Server Connection Method
|
||||
|
@ -3798,13 +3896,13 @@ export default {
|
|||
|
||||
<div class="field">
|
||||
<label class="label mb-0">
|
||||
Advanced
|
||||
Apple compatibility mode
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
v-model="prefs.appleCompat"
|
||||
:value="true">
|
||||
Apple compatibility mode (iPad, iPhone, Safari)
|
||||
Check this box if you are on an iPad, iPhone, or Safari browser
|
||||
</label>
|
||||
<p class="help">
|
||||
If you experience difficulty opening cameras and you are on an Apple device (iPad,
|
||||
|
|
Loading…
Reference in New Issue
Block a user