forked from binbat/woom
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
325 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package v1 | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"woom/server/model" | ||
|
||
"github.com/redis/go-redis/v9" | ||
) | ||
|
||
func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
|
||
// 解析请求体中的JSON数据 | ||
var inviteData struct { | ||
MeetingId string `json:"meetingId"` | ||
InviterId string `json:"inviterId"` | ||
InviteeId string `json:"inviteeId"` | ||
} | ||
|
||
if err := json.NewDecoder(r.Body).Decode(&inviteData); err != nil { | ||
http.Error(w, "Invalid request payload", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
inviteValue := fmt.Sprintf("%s %s", inviteData.MeetingId, inviteData.InviterId) | ||
|
||
err := h.rdb.HSet(ctx, model.InvitationKey, inviteData.InviteeId, inviteValue).Err() | ||
if err != nil { | ||
log.Printf("Failed to save invitation for user %s: %v", inviteData.InviteeId, err) | ||
http.Error(w, "Failed to store invitation", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(http.StatusOK) | ||
response := map[string]string{ | ||
"success": "true", | ||
"message": "Invitation sent successfully", | ||
} | ||
if err := json.NewEncoder(w).Encode(response); err != nil { | ||
log.Printf("Failed to encode response: %v", err) | ||
http.Error(w, "Failed to send response", http.StatusInternalServerError) | ||
} | ||
} | ||
|
||
func (h *Handler) GetInvitation(w http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
|
||
var reqBody struct { | ||
InviteeId string `json:"inviteeId"` | ||
} | ||
|
||
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | ||
http.Error(w, "Invalid request body", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
inviteeId := reqBody.InviteeId | ||
value, err := h.rdb.HGet(ctx, model.InvitationKey, inviteeId).Result() | ||
|
||
if err == redis.Nil { | ||
return | ||
} else if err != nil { | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
if err := json.NewEncoder(w).Encode(map[string]string{"value": value}); err != nil { | ||
http.Error(w, "Failed to encode response", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
if err := h.rdb.HDel(ctx, model.InvitationKey, inviteeId).Err(); err != nil { | ||
log.Printf("Failed to delete inviteeId %s from invitation: %v\n", inviteeId, err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,37 @@ | ||
import { useState } from 'react' | ||
import { sendInvite } from '../lib/api' | ||
|
||
interface InviteProps { | ||
meetingId: string; | ||
inviterId: string; | ||
inviteeId: string; | ||
} | ||
|
||
export default function Invite({ meetingId, inviterId, inviteeId }: InviteProps) { | ||
const [isInvited, setIsInvited] = useState<boolean>(false) | ||
|
||
const handleInvite = async () => { | ||
try { | ||
const response = await sendInvite(meetingId, inviterId, inviteeId) | ||
if (response.success) { | ||
setIsInvited(true) | ||
console.log('Invite sent successfully') | ||
} else { | ||
console.error('Failed to send invite') | ||
} | ||
} catch (error) { | ||
console.error('Error sending invite:', error) | ||
} | ||
} | ||
|
||
return ( | ||
<div> | ||
<button | ||
onClick={handleInvite} | ||
className={`bg-blue-500 text-white p-2 rounded-md ${isInvited ? 'bg-green-500' : ''}`} | ||
> | ||
{isInvited ? 'Invited' : 'Invite'} | ||
</button> | ||
</div> | ||
) | ||
} | ||
import { useState } from 'react' | ||
import { sendInvite } from '../lib/api' | ||
|
||
interface InviteProps { | ||
meetingId: string; | ||
inviterId: string; | ||
inviteeId: string; | ||
} | ||
|
||
export default function Invite({ meetingId, inviterId, inviteeId }: InviteProps) { | ||
const [isInvited, setIsInvited] = useState<boolean>(false) | ||
|
||
const handleInvite = async () => { | ||
try { | ||
const response = await sendInvite(meetingId, inviterId, inviteeId) | ||
if (response.success) { | ||
setIsInvited(true) | ||
console.log('Invite sent successfully') | ||
} else { | ||
console.error('Failed to send invite') | ||
} | ||
} catch (error) { | ||
console.error('Error sending invite:', error) | ||
} | ||
} | ||
|
||
return ( | ||
<div> | ||
<button | ||
onClick={handleInvite} | ||
className={`bg-blue-500 text-white p-2.8 rounded-md ${isInvited ? 'bg-green-500' : ''}`} | ||
> | ||
{isInvited ? 'Invited' : 'Invite'} | ||
</button> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,82 +1,100 @@ | ||
import { useEffect, useState } from 'react' | ||
import { getUserOnlineStatus, updateUserStatus } from '../lib/api' | ||
import { getStorage } from '../lib/storage' | ||
|
||
export default function UserList() { | ||
const [userStatus, setUserStatus] = useState<{ [userId: string]: string }>({}) | ||
const [isOpen, setIsOpen] = useState<boolean>(false) | ||
|
||
const fetchUserStatus = async () => { | ||
try { | ||
const status = await getUserOnlineStatus() | ||
setUserStatus(status) | ||
} catch (error) { | ||
console.error('Failed to fetch user status:', error) | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
fetchUserStatus() | ||
const interval = setInterval(fetchUserStatus, 5000) | ||
return () => clearInterval(interval) | ||
}, []) | ||
|
||
useEffect(() => { | ||
const cleanup = async () => { | ||
const userId = getStorage()?.userId | ||
updateUserStatus(userId, '0') | ||
} | ||
|
||
window.addEventListener('beforeunload', cleanup) | ||
window.addEventListener('unload', cleanup) | ||
return () => { | ||
window.removeEventListener('beforeunload', cleanup) | ||
window.removeEventListener('unload', cleanup) | ||
} | ||
}, []) | ||
|
||
const sortedUserStatus = Object.keys(userStatus) | ||
.sort((_, b) => (userStatus[b] === '1' ? 1 : -1)) | ||
.map((userId) => ({ | ||
userId, | ||
status: userStatus[userId], | ||
})) | ||
|
||
return ( | ||
<div className="relative"> | ||
<button | ||
onClick={() => setIsOpen(!isOpen)} | ||
className="absolute bottom-4 right-4 bg-blue-500 text-white p-3 rounded-full shadow-lg" | ||
> | ||
{isOpen ? ( | ||
<span className="text-xl">▲</span> | ||
) : ( | ||
<span className="text-xl">▼</span> | ||
)} | ||
</button> | ||
|
||
{isOpen && ( | ||
<div className="absolute bottom-16 right-4 bg-white p-4 rounded-lg shadow-lg max-w-xs w-full max-h-[200px] overflow-y-auto"> | ||
<h3 className="font-bold mb-2">User Online Status</h3> | ||
<ul className="space-y-2"> | ||
{sortedUserStatus.map(({ userId, status }) => ( | ||
<li key={userId} className="flex items-center justify-between space-x-1"> | ||
<span className="font-bold text-lg text-blue-600">{userId}</span> | ||
<div className="flex items-center space-x-2"> | ||
{status === '1' ? ( | ||
<span className="text-green-500">✔️</span> | ||
) : ( | ||
<span className="text-red-500">❌</span> | ||
)} | ||
<span className={status === 'true' ? 'text-green-500' : 'text-red-500'}> | ||
{status === '1' ? 'Online' : 'Offline'} | ||
</span> | ||
</div> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
)} | ||
</div> | ||
) | ||
} | ||
import { useEffect, useState } from 'react' | ||
import { useAtom } from 'jotai' | ||
import { getUserOnlineStatus, updateUserStatus } from '../lib/api' | ||
import { getStorage } from '../lib/storage' | ||
import { meetingIdAtom } from '../store/atom' | ||
import Invite from './Invite' | ||
|
||
export default function UserList() { | ||
const [userStatus, setUserStatus] = useState<{ [userId: string]: string }>({}) | ||
const [isOpen, setIsOpen] = useState<boolean>(false) | ||
|
||
const [meeting] = useAtom(meetingIdAtom) | ||
|
||
const inviterId = getStorage()?.userId | ||
const meetingId = getStorage()?.meeting | ||
|
||
const fetchUserStatus = async () => { | ||
try { | ||
const status = await getUserOnlineStatus() | ||
setUserStatus(status) | ||
} catch (error) { | ||
console.error('Failed to fetch user status:', error) | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
fetchUserStatus() | ||
const interval = setInterval(fetchUserStatus, 5000) | ||
return () => clearInterval(interval) | ||
}, []) | ||
|
||
useEffect(() => { | ||
const cleanup = async () => { | ||
updateUserStatus(inviterId, '0') | ||
} | ||
|
||
window.addEventListener('beforeunload', cleanup) | ||
window.addEventListener('unload', cleanup) | ||
return () => { | ||
window.removeEventListener('beforeunload', cleanup) | ||
window.removeEventListener('unload', cleanup) | ||
} | ||
}, []) | ||
|
||
const sortedUserStatus = Object.keys(userStatus) | ||
.sort((_, b) => (userStatus[b] === '1' ? 1 : -1)) | ||
.map((userId) => ({ | ||
userId, | ||
status: userStatus[userId], | ||
})) | ||
|
||
return ( | ||
<div className="relative"> | ||
<button | ||
onClick={() => setIsOpen(!isOpen)} | ||
className="absolute bottom-4 right-4 bg-blue-500 text-white p-3 rounded-full shadow-lg" | ||
> | ||
{isOpen ? ( | ||
<span className="text-xl">▲</span> | ||
) : ( | ||
<span className="text-xl">▼</span> | ||
)} | ||
</button> | ||
|
||
{isOpen && ( | ||
<div className="absolute bottom-16 right-4 bg-white p-4 rounded-lg shadow-lg max-w-xs w-full max-h-[200px] overflow-y-auto"> | ||
<h3 className="font-bold mb-2">User Online Status</h3> | ||
<ul className="space-y-2"> | ||
{sortedUserStatus.map(({ userId, status }) => ( | ||
<li key={userId} className="flex items-center justify-between space-x-1"> | ||
<span className="font-bold text-lg text-blue-600">{userId}</span> | ||
|
||
<div className="flex items-center space-x-2"> | ||
{status === '1' ? ( | ||
<span className="text-green-500">✔️</span> | ||
) : ( | ||
<span className="text-red-500">❌</span> | ||
)} | ||
<span className={status === 'true' ? 'text-green-500' : 'text-red-500'}> | ||
{status === '1' ? 'Online' : 'Offline'} | ||
</span> | ||
</div> | ||
|
||
{status === '1' && meeting ? ( | ||
<Invite | ||
meetingId={meetingId} | ||
inviterId={inviterId} | ||
inviteeId={userId} | ||
/> | ||
) : ( | ||
<span className="text-gray-500">Disabled</span> | ||
)} | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
)} | ||
</div> | ||
) | ||
} |
Oops, something went wrong.