feat: add MCP server with resources, tools, auth, and Traefik routing
- Add backend/app/mcp_server.py with FastMCP server (hms-cms) - Resources: design-rules, page-structure, sync-status - Tools: trigger_sync, get_sync_log, set_rentman_token, read_file, write_file, create_page, deploy, git_commit, git_status - Bearer token auth via MCP_AUTH_TOKEN env var - Mount MCP at /mcp with streamable HTTP transport - Integrate session manager in FastAPI lifespan - Add Traefik labels for external /mcp route on hms.media-on.de - Add git, docker.io, curl to backend Dockerfile - Mount repo root + docker socket in backend container - Add mcp>=1.0.0 to requirements.txt
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type { EquipmentItem } from "~/composables/useEquipment";
|
||||
|
||||
/**
|
||||
* Cart item structure stored in the Pinia cart store.
|
||||
*/export interface CartItem {
|
||||
equipment_id: number;
|
||||
name: string;
|
||||
quantity: number;
|
||||
rental_price: number | null;
|
||||
image_url: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form payload for rental request submission.
|
||||
*/export interface RentalRequestPayload {
|
||||
event_name: string;
|
||||
date_start: string;
|
||||
date_end: string;
|
||||
location: string;
|
||||
person_count: number;
|
||||
contact_name: string;
|
||||
contact_company: string;
|
||||
contact_email: string;
|
||||
contact_phone: string;
|
||||
contact_street: string;
|
||||
contact_postalcode: string;
|
||||
contact_city: string;
|
||||
message: string;
|
||||
items: { equipment_id: number; quantity: number }[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from POST /api/rental-requests.
|
||||
*/export interface RentalRequestResponse {
|
||||
reference_number: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pinia store for the equipment rental cart.
|
||||
* Persists to localStorage via plugins/pinia-persist.client.ts.
|
||||
*/export const useCartStore = defineStore("cart", {
|
||||
state: (): { items: CartItem[] } => ({
|
||||
items: [] as CartItem[],
|
||||
}),
|
||||
|
||||
getters: {
|
||||
/**
|
||||
* Total count of all items (sum of quantities).
|
||||
*/ totalCount(state): number {
|
||||
return state.items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the cart is empty.
|
||||
*/ isEmpty(state): boolean {
|
||||
return state.items.length === 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the cart has items.
|
||||
*/ hasItems(state): boolean {
|
||||
return state.items.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Items as formatted payload for rental request API.
|
||||
*/ apiItems(state): { equipment_id: number; quantity: number }[] {
|
||||
return state.items.map((item) => ({
|
||||
equipment_id: item.equipment_id,
|
||||
quantity: item.quantity,
|
||||
}));
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Add an equipment item to the cart. If it already exists, increment quantity.
|
||||
*/ addItem(equipment: EquipmentItem): void {
|
||||
const existing = this.items.find(
|
||||
(item) => item.equipment_id === equipment.id,
|
||||
);
|
||||
if (existing) {
|
||||
existing.quantity += 1;
|
||||
} else {
|
||||
this.items.push({
|
||||
equipment_id: equipment.id,
|
||||
name: equipment.name,
|
||||
quantity: 1,
|
||||
rental_price: equipment.rental_price,
|
||||
image_url: equipment.image_url,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an item by explicit fields (used from detail page where full EquipmentItem may not be available).
|
||||
*/ addItemByFields(item: {
|
||||
equipment_id: number;
|
||||
name: string;
|
||||
rental_price: number | null;
|
||||
image_url: string | null;
|
||||
}): void {
|
||||
const existing = this.items.find(
|
||||
(i) => i.equipment_id === item.equipment_id,
|
||||
);
|
||||
if (existing) {
|
||||
existing.quantity += 1;
|
||||
} else {
|
||||
this.items.push({
|
||||
equipment_id: item.equipment_id,
|
||||
name: item.name,
|
||||
quantity: 1,
|
||||
rental_price: item.rental_price,
|
||||
image_url: item.image_url,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an item from the cart by equipment_id.
|
||||
*/ removeItem(equipmentId: number): void {
|
||||
const idx = this.items.findIndex(
|
||||
(item) => item.equipment_id === equipmentId,
|
||||
);
|
||||
if (idx !== -1) {
|
||||
this.items.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the quantity of a cart item. If quantity <= 0, remove the item.
|
||||
*/ updateQuantity(equipmentId: number, quantity: number): void {
|
||||
if (quantity <= 0) {
|
||||
this.removeItem(equipmentId);
|
||||
return;
|
||||
}
|
||||
const item = this.items.find(
|
||||
(i) => i.equipment_id === equipmentId,
|
||||
);
|
||||
if (item) {
|
||||
item.quantity = quantity;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Increment quantity of a cart item.
|
||||
*/ incrementQuantity(equipmentId: number): void {
|
||||
const item = this.items.find(
|
||||
(i) => i.equipment_id === equipmentId,
|
||||
);
|
||||
if (item) {
|
||||
item.quantity += 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrement quantity of a cart item. If quantity reaches 0, remove it.
|
||||
*/ decrementQuantity(equipmentId: number): void {
|
||||
const item = this.items.find(
|
||||
(i) => i.equipment_id === equipmentId,
|
||||
);
|
||||
if (item) {
|
||||
item.quantity -= 1;
|
||||
if (item.quantity <= 0) {
|
||||
this.removeItem(equipmentId);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all items from the cart.
|
||||
*/ clearCart(): void {
|
||||
this.items = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user