Compare commits

...

7 Commits

Author SHA1 Message Date
95c330568d feat: UI bind api service toggling 2025-06-03 00:07:09 +02:00
900d314a95 feat: don't require view service id 2025-06-03 00:07:00 +02:00
0d8a3b1b39 feat: store toggle active api service 2025-06-03 00:05:43 +02:00
4b7396c210 feat: toggle api service API 2025-06-03 00:05:32 +02:00
d4e2cbdd4f feat: activate api service query 2025-06-03 00:05:20 +02:00
5024ac8151 feat: register toggle endpoint 2025-06-03 00:05:05 +02:00
3bf08c5933 feat: toggle api service endpoint 2025-06-03 00:04:36 +02:00
7 changed files with 90 additions and 3 deletions

View File

@ -283,3 +283,36 @@ func (h *AdminHandler) UpdateApiService(w http.ResponseWriter, r *http.Request)
web.Error(w, "failed to send updated api service", http.StatusInternalServerError)
}
}
func (h *AdminHandler) ToggleApiService(w http.ResponseWriter, r *http.Request) {
var err error
serviceId := chi.URLParam(r, "id")
parsed, err := uuid.Parse(serviceId)
if err != nil {
web.Error(w, "provided service id is not valid", http.StatusBadRequest)
return
}
service, err := h.repo.GetApiServiceId(r.Context(), parsed)
if err != nil {
web.Error(w, "service with provided id not found", http.StatusNotFound)
return
}
if service.IsActive {
log.Println("INFO: Service is active. Deactivating...")
err = h.repo.DeactivateApiService(r.Context(), service.ClientID)
} else {
log.Println("INFO: Service is inactive. Activating...")
err = h.repo.ActivateApiService(r.Context(), service.ClientID)
}
if err != nil {
log.Printf("ERR: Failed to toggle api service (cid: %s): %v\n", service.ClientID, err)
web.Error(w, "failed to toggle api service", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}

View File

@ -30,5 +30,6 @@ func (h *AdminHandler) RegisterRoutes(router chi.Router) {
r.Post("/api-services", h.AddApiService)
r.Patch("/api-services/{id}", h.RegenerateApiServiceSecret)
r.Put("/api-services/{id}", h.RegenerateApiServiceSecret)
r.Patch("/api-services/toggle/{id}", h.ToggleApiService)
})
}

View File

@ -12,6 +12,18 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const activateApiService = `-- name: ActivateApiService :exec
UPDATE api_services
SET is_active = true,
updated_at = NOW()
WHERE client_id = $1
`
func (q *Queries) ActivateApiService(ctx context.Context, clientID string) error {
_, err := q.db.Exec(ctx, activateApiService, clientID)
return err
}
const createApiService = `-- name: CreateApiService :one
INSERT INTO api_services (
client_id, client_secret, name, description, redirect_uris, scopes, grant_types, is_active

View File

@ -38,6 +38,12 @@ SET is_active = false,
updated_at = NOW()
WHERE client_id = $1;
-- name: ActivateApiService :exec
UPDATE api_services
SET is_active = true,
updated_at = NOW()
WHERE client_id = $1;
-- name: UpdateClientSecret :exec
UPDATE api_services
SET client_secret = $2,

View File

@ -55,3 +55,12 @@ export const getApiService = async (id: string): Promise<ApiService> => {
return response.data;
};
export const patchToggleApiService = async (id: string): Promise<void> => {
const response = await axios.patch(`/api/v1/admin/api-services/toggle/${id}`);
if (response.status !== 200 && response.status !== 201)
throw await handleApiError(response);
return response.data;
};

View File

@ -2,7 +2,7 @@ import Breadcrumbs from "@/components/ui/breadcrumbs";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useAdmin } from "@/store/admin";
import { useEffect, type FC } from "react";
import { useCallback, useEffect, type FC } from "react";
import { Link, useParams } from "react-router";
const InfoCard = ({
@ -29,6 +29,9 @@ const ViewApiServicePage: FC = () => {
const loadService = useAdmin((state) => state.fetchApiService);
const toggling = useAdmin((state) => state.togglingApiService);
const toggle = useAdmin((state) => state.toggleApiService);
useEffect(() => {
if (typeof serviceId === "string") loadService(serviceId);
}, [loadService, serviceId]);
@ -181,7 +184,8 @@ const ViewApiServicePage: FC = () => {
? "text-red-400 hover:text-red-500"
: "text-green-400 hover:text-green-500"
}
onClick={() => {}}
onClick={toggle}
loading={toggling}
>
{apiService.is_active ? "Disable" : "Enable"}
</Button>

View File

@ -1,6 +1,7 @@
import {
getApiService,
getApiServices,
patchToggleApiService,
postApiService,
type CreateApiServiceRequest,
} from "@/api/admin/apiServices";
@ -21,9 +22,12 @@ interface IAdminState {
fetchApiService: (id: string) => Promise<void>;
createApiService: (req: CreateApiServiceRequest) => Promise<void>;
resetCredentials: () => void;
togglingApiService: boolean;
toggleApiService: () => Promise<void>;
}
export const useAdmin = create<IAdminState>((set) => ({
export const useAdmin = create<IAdminState>((set, get) => ({
apiServices: [],
loadingApiServices: false,
@ -33,6 +37,8 @@ export const useAdmin = create<IAdminState>((set) => ({
viewApiService: null,
fetchingApiService: false,
togglingApiService: false,
resetCredentials: () => set({ createdCredentials: null }),
fetchApiServices: async () => {
@ -61,6 +67,22 @@ export const useAdmin = create<IAdminState>((set) => ({
}
},
toggleApiService: async () => {
const viewService = get().viewApiService;
if (!viewService) return;
set({ togglingApiService: true });
try {
await patchToggleApiService(viewService.id);
get().fetchApiService(viewService.id);
} catch (err) {
console.log("ERR: Failed to toggle service:", err);
} finally {
set({ togglingApiService: false });
}
},
createApiService: async (req: CreateApiServiceRequest) => {
set({ creatingApiService: true });