6.23.0: Архивация досок желаний и товаров
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m26s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m26s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -576,6 +576,7 @@ type WishlistBoard struct {
|
||||
InviteURL *string `json:"invite_url,omitempty"`
|
||||
MemberCount int `json:"member_count"`
|
||||
IsOwner bool `json:"is_owner"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -4914,6 +4915,7 @@ func main() {
|
||||
// Wishlist Boards (ВАЖНО: должны быть ПЕРЕД /api/wishlist/{id} чтобы избежать конфликта роутов!)
|
||||
protected.HandleFunc("/api/wishlist/boards", app.getBoardsHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards", app.createBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/archived", app.getArchivedBoardsHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}", app.getBoardHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}", app.updateBoardHandler).Methods("PUT", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}", app.deleteBoardHandler).Methods("DELETE", "OPTIONS")
|
||||
@@ -4921,6 +4923,8 @@ func main() {
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}/members", app.getBoardMembersHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}/members/{userId}", app.removeBoardMemberHandler).Methods("DELETE", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}/leave", app.leaveBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}/archive", app.archiveBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{id}/unarchive", app.unarchiveBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{boardId}/items", app.getBoardItemsHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{boardId}/items", app.createBoardItemHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/boards/{boardId}/completed", app.getBoardCompletedHandler).Methods("GET", "OPTIONS")
|
||||
@@ -4930,6 +4934,7 @@ func main() {
|
||||
// Shopping Boards (Товары)
|
||||
protected.HandleFunc("/api/shopping/boards", app.getShoppingBoardsHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards", app.createShoppingBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/archived", app.getArchivedShoppingBoardsHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}", app.getShoppingBoardHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}", app.updateShoppingBoardHandler).Methods("PUT", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}", app.deleteShoppingBoardHandler).Methods("DELETE", "OPTIONS")
|
||||
@@ -4937,6 +4942,8 @@ func main() {
|
||||
protected.HandleFunc("/api/shopping/boards/{id}/members", app.getShoppingBoardMembersHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}/members/{userId}", app.removeShoppingBoardMemberHandler).Methods("DELETE", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}/leave", app.leaveShoppingBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}/archive", app.archiveShoppingBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{id}/unarchive", app.unarchiveShoppingBoardHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{boardId}/items", app.getShoppingItemsHandler).Methods("GET", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/boards/{boardId}/items", app.createShoppingItemHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/shopping/items/{id}", app.getShoppingItemHandler).Methods("GET", "OPTIONS")
|
||||
@@ -16072,7 +16079,7 @@ func (a *App) getBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
boards := []WishlistBoard{}
|
||||
|
||||
// Получаем свои доски + доски где пользователь участник
|
||||
// Получаем свои доски + доски где пользователь участник (с флагом архивации)
|
||||
rows, err := a.DB.Query(`
|
||||
SELECT DISTINCT
|
||||
wb.id,
|
||||
@@ -16083,11 +16090,12 @@ func (a *App) getBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
wb.invite_token,
|
||||
wb.created_at,
|
||||
(SELECT COUNT(*) FROM wishlist_board_members wbm WHERE wbm.board_id = wb.id) as member_count,
|
||||
(wb.owner_id = $1) as is_owner
|
||||
(wb.owner_id = $1) as is_owner,
|
||||
EXISTS (SELECT 1 FROM board_archives ba WHERE ba.user_id = $1 AND ba.board_type = 'wishlist' AND ba.board_id = wb.id) as is_archived
|
||||
FROM wishlist_boards wb
|
||||
JOIN users u ON wb.owner_id = u.id
|
||||
LEFT JOIN wishlist_board_members wbm ON wb.id = wbm.board_id
|
||||
WHERE wb.deleted = FALSE
|
||||
WHERE wb.deleted = FALSE
|
||||
AND (wb.owner_id = $1 OR wbm.user_id = $1)
|
||||
ORDER BY is_owner DESC, wb.created_at DESC
|
||||
`, userID)
|
||||
@@ -16113,6 +16121,7 @@ func (a *App) getBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&board.CreatedAt,
|
||||
&board.MemberCount,
|
||||
&board.IsOwner,
|
||||
&board.IsArchived,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning board: %v", err)
|
||||
@@ -16264,6 +16273,11 @@ func (a *App) getBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, архивирована ли доска для пользователя
|
||||
var isArchived bool
|
||||
a.DB.QueryRow(`SELECT EXISTS(SELECT 1 FROM board_archives WHERE user_id = $1 AND board_type = 'wishlist' AND board_id = $2)`, userID, boardID).Scan(&isArchived)
|
||||
board.IsArchived = isArchived
|
||||
|
||||
// Invite token и URL только для владельца
|
||||
if board.IsOwner && inviteToken.Valid {
|
||||
board.InviteToken = &inviteToken.String
|
||||
@@ -16638,6 +16652,158 @@ func (a *App) leaveBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// archiveBoardHandler архивирует доску желаний для текущего пользователя
|
||||
func (a *App) archiveBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
boardID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
sendErrorWithCORS(w, "Invalid board ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем что пользователь имеет доступ к доске (владелец или участник)
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`SELECT owner_id FROM wishlist_boards WHERE id = $1 AND deleted = FALSE`, boardID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows {
|
||||
sendErrorWithCORS(w, "Board not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if ownerID != userID {
|
||||
var memberCount int
|
||||
a.DB.QueryRow(`SELECT COUNT(*) FROM wishlist_board_members WHERE board_id = $1 AND user_id = $2`, boardID, userID).Scan(&memberCount)
|
||||
if memberCount == 0 {
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`
|
||||
INSERT INTO board_archives (user_id, board_type, board_id)
|
||||
VALUES ($1, 'wishlist', $2)
|
||||
ON CONFLICT (user_id, board_type, board_id) DO NOTHING
|
||||
`, userID, boardID)
|
||||
if err != nil {
|
||||
log.Printf("Error archiving board: %v", err)
|
||||
sendErrorWithCORS(w, "Error archiving board", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// unarchiveBoardHandler разархивирует доску желаний для текущего пользователя
|
||||
func (a *App) unarchiveBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
boardID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
sendErrorWithCORS(w, "Invalid board ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`DELETE FROM board_archives WHERE user_id = $1 AND board_type = 'wishlist' AND board_id = $2`, userID, boardID)
|
||||
if err != nil {
|
||||
log.Printf("Error unarchiving board: %v", err)
|
||||
sendErrorWithCORS(w, "Error unarchiving board", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// getArchivedBoardsHandler возвращает архивированные доски желаний пользователя
|
||||
func (a *App) getArchivedBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
boards := []WishlistBoard{}
|
||||
|
||||
rows, err := a.DB.Query(`
|
||||
SELECT DISTINCT
|
||||
wb.id,
|
||||
wb.owner_id,
|
||||
COALESCE(u.name, u.email) as owner_name,
|
||||
wb.name,
|
||||
wb.invite_enabled,
|
||||
wb.invite_token,
|
||||
wb.created_at,
|
||||
(SELECT COUNT(*) FROM wishlist_board_members wbm WHERE wbm.board_id = wb.id) as member_count,
|
||||
(wb.owner_id = $1) as is_owner
|
||||
FROM wishlist_boards wb
|
||||
JOIN users u ON wb.owner_id = u.id
|
||||
LEFT JOIN wishlist_board_members wbm ON wb.id = wbm.board_id
|
||||
JOIN board_archives ba ON ba.board_id = wb.id AND ba.board_type = 'wishlist' AND ba.user_id = $1
|
||||
WHERE wb.deleted = FALSE
|
||||
AND (wb.owner_id = $1 OR wbm.user_id = $1)
|
||||
ORDER BY wb.created_at DESC
|
||||
`, userID)
|
||||
if err != nil {
|
||||
log.Printf("Error getting archived boards: %v", err)
|
||||
sendErrorWithCORS(w, "Error getting archived boards", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var board WishlistBoard
|
||||
var inviteToken sql.NullString
|
||||
err := rows.Scan(
|
||||
&board.ID,
|
||||
&board.OwnerID,
|
||||
&board.OwnerName,
|
||||
&board.Name,
|
||||
&board.InviteEnabled,
|
||||
&inviteToken,
|
||||
&board.CreatedAt,
|
||||
&board.MemberCount,
|
||||
&board.IsOwner,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning archived board: %v", err)
|
||||
continue
|
||||
}
|
||||
boards = append(boards, board)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(boards)
|
||||
}
|
||||
|
||||
// getBoardInviteInfoHandler возвращает информацию о доске по invite token
|
||||
func (a *App) getBoardInviteInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
@@ -19023,6 +19189,7 @@ type ShoppingBoard struct {
|
||||
InviteURL *string `json:"invite_url,omitempty"`
|
||||
MemberCount int `json:"member_count"`
|
||||
IsOwner bool `json:"is_owner"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -19097,7 +19264,8 @@ func (a *App) getShoppingBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sb.invite_token,
|
||||
sb.created_at,
|
||||
(SELECT COUNT(*) FROM shopping_board_members sbm WHERE sbm.board_id = sb.id) as member_count,
|
||||
(sb.owner_id = $1) as is_owner
|
||||
(sb.owner_id = $1) as is_owner,
|
||||
EXISTS (SELECT 1 FROM board_archives ba WHERE ba.user_id = $1 AND ba.board_type = 'shopping' AND ba.board_id = sb.id) as is_archived
|
||||
FROM shopping_boards sb
|
||||
JOIN users u ON sb.owner_id = u.id
|
||||
LEFT JOIN shopping_board_members sbm ON sb.id = sbm.board_id
|
||||
@@ -19127,6 +19295,7 @@ func (a *App) getShoppingBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&board.CreatedAt,
|
||||
&board.MemberCount,
|
||||
&board.IsOwner,
|
||||
&board.IsArchived,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning shopping board: %v", err)
|
||||
@@ -19275,6 +19444,11 @@ func (a *App) getShoppingBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, архивирована ли доска для пользователя
|
||||
var isShoppingArchived bool
|
||||
a.DB.QueryRow(`SELECT EXISTS(SELECT 1 FROM board_archives WHERE user_id = $1 AND board_type = 'shopping' AND board_id = $2)`, userID, boardID).Scan(&isShoppingArchived)
|
||||
board.IsArchived = isShoppingArchived
|
||||
|
||||
if board.IsOwner && inviteToken.Valid {
|
||||
board.InviteToken = &inviteToken.String
|
||||
baseURL := getEnv("WEBHOOK_BASE_URL", "")
|
||||
@@ -19637,6 +19811,157 @@ func (a *App) leaveShoppingBoardHandler(w http.ResponseWriter, r *http.Request)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// archiveShoppingBoardHandler архивирует доску покупок для текущего пользователя
|
||||
func (a *App) archiveShoppingBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
boardID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
sendErrorWithCORS(w, "Invalid board ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`SELECT owner_id FROM shopping_boards WHERE id = $1 AND deleted = FALSE`, boardID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows {
|
||||
sendErrorWithCORS(w, "Board not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if ownerID != userID {
|
||||
var memberCount int
|
||||
a.DB.QueryRow(`SELECT COUNT(*) FROM shopping_board_members WHERE board_id = $1 AND user_id = $2`, boardID, userID).Scan(&memberCount)
|
||||
if memberCount == 0 {
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`
|
||||
INSERT INTO board_archives (user_id, board_type, board_id)
|
||||
VALUES ($1, 'shopping', $2)
|
||||
ON CONFLICT (user_id, board_type, board_id) DO NOTHING
|
||||
`, userID, boardID)
|
||||
if err != nil {
|
||||
log.Printf("Error archiving shopping board: %v", err)
|
||||
sendErrorWithCORS(w, "Error archiving board", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// unarchiveShoppingBoardHandler разархивирует доску покупок для текущего пользователя
|
||||
func (a *App) unarchiveShoppingBoardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
boardID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
sendErrorWithCORS(w, "Invalid board ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`DELETE FROM board_archives WHERE user_id = $1 AND board_type = 'shopping' AND board_id = $2`, userID, boardID)
|
||||
if err != nil {
|
||||
log.Printf("Error unarchiving shopping board: %v", err)
|
||||
sendErrorWithCORS(w, "Error unarchiving board", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// getArchivedShoppingBoardsHandler возвращает архивированные доски покупок пользователя
|
||||
func (a *App) getArchivedShoppingBoardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
boards := []ShoppingBoard{}
|
||||
|
||||
rows, err := a.DB.Query(`
|
||||
SELECT DISTINCT
|
||||
sb.id,
|
||||
sb.owner_id,
|
||||
COALESCE(u.name, u.email) as owner_name,
|
||||
sb.name,
|
||||
sb.invite_enabled,
|
||||
sb.invite_token,
|
||||
sb.created_at,
|
||||
(SELECT COUNT(*) FROM shopping_board_members sbm WHERE sbm.board_id = sb.id) as member_count,
|
||||
(sb.owner_id = $1) as is_owner
|
||||
FROM shopping_boards sb
|
||||
JOIN users u ON sb.owner_id = u.id
|
||||
LEFT JOIN shopping_board_members sbm ON sb.id = sbm.board_id
|
||||
JOIN board_archives ba ON ba.board_id = sb.id AND ba.board_type = 'shopping' AND ba.user_id = $1
|
||||
WHERE sb.deleted = FALSE
|
||||
AND (sb.owner_id = $1 OR sbm.user_id = $1)
|
||||
ORDER BY sb.created_at DESC
|
||||
`, userID)
|
||||
if err != nil {
|
||||
log.Printf("Error getting archived shopping boards: %v", err)
|
||||
sendErrorWithCORS(w, "Error getting archived boards", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var board ShoppingBoard
|
||||
var inviteToken sql.NullString
|
||||
err := rows.Scan(
|
||||
&board.ID,
|
||||
&board.OwnerID,
|
||||
&board.OwnerName,
|
||||
&board.Name,
|
||||
&board.InviteEnabled,
|
||||
&inviteToken,
|
||||
&board.CreatedAt,
|
||||
&board.MemberCount,
|
||||
&board.IsOwner,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning archived shopping board: %v", err)
|
||||
continue
|
||||
}
|
||||
boards = append(boards, board)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(boards)
|
||||
}
|
||||
|
||||
// getShoppingBoardInviteInfoHandler возвращает информацию о доске покупок по invite token
|
||||
func (a *App) getShoppingBoardInviteInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
|
||||
Reference in New Issue
Block a user