Week 3 Day 05 Hospitality Domain Generated by Gemini Code Assist

GCP Deployment Guide

Deploying the Digital Hotel Concierge application on Google Cloud Platform.

1. GCP Configuration & User Management

This solution outlines the deployment of the Digital Hotel Concierge application on Google Cloud Platform (GCP). It integrates User Interface (Flutter), Business Logic (Cloud Functions), Data Storage (Firestore), and Security (API Gateway + Firebase Auth).

1

Create Project

Go to the GCP Console and create a new project.

2

Enable Firebase

Navigate to "Firebase" in the menu and link your GCP project.

3

Enable Authentication

Go to Firebase Console > Authentication > Sign-in method. Enable Email/Password (as the primary method for guests and staff).

4

Database Setup

Go to Firestore Database and click "Create Database". Start in Production Mode and apply the Security Rules.

User Role Management

Users have roles ('GUEST', 'STAFF'). In Firebase Auth, we will use Custom Claims to handle this securely via the Firebase Admin SDK.

2. Serverless Business Logic (Cloud Functions)

We will create two primary functions to handle the "Customer Flow" and the "Staff Flow".

File: index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

// 1. GUEST LOGIC: Create Service Request
exports.createServiceRequest = functions.https.onRequest(async (req, res) => {
    // Basic Auth Check (In production, validate JWT from API Gateway)
    const { guest_id, service_type, details } = req.body;

    try {
        // Create Master Request
        const requestRef = await db.collection('master_requests').add({
            guest_id: guest_id,
            service_type: service_type, // ENUM: HOUSEKEEPING, DINING, etc.
            status: 'PENDING',
            created_at: admin.firestore.FieldValue.serverTimestamp(),
            assigned_staff_id: null
        });

        // Create Service Specific Detail (e.g., Housekeeping_Details)
        await db.collection('housekeeping_details').add({
            request_id: requestRef.id,
            ...details // item_name, quantity, allergy_note
        });

        res.status(200).json({ result: 'success', request_id: requestRef.id });
    } catch (error) {
        res.status(500).send(error.message);
    }
});

// 2. STAFF LOGIC: Update Request Status
exports.updateRequestStatus = functions.https.onRequest(async (req, res) => {
    const { request_id, staff_id, new_status } = req.body;

    try {
        const updateData = { status: new_status }; // 'ASSIGNED' or 'COMPLETED'
        
        // If accepting task, lock it to staff member
        if (new_status === 'ASSIGNED') {
            updateData.assigned_staff_id = staff_id;
        }

        await db.collection('master_requests').doc(request_id).update(updateData);
        
        // Trigger Notification Logic
        if (new_status === 'COMPLETED') {
            console.log(`Notify Guest: Request ${request_id} is complete.`);
        }

        res.status(200).json({ result: 'updated' });
    } catch (error) {
        res.status(500).send(error.message);
    }
});

3. API Gateway Configuration

We will define an OpenAPI 2.0 specification to route traffic to the Cloud Functions securely.

File: openapi2-functions.yaml
swagger: '2.0'
info:
  title: Hotel Concierge API
  description: API for Guest Requests and Staff Management
  version: 1.0.0
schemes:
  - https
produces:
  - application/json
securityDefinitions:
  # Secure via API Key as requested
  api_key:
    type: "apiKey"
    name: "key"
    in: "query"
  # Maximum Security: Google ID Token (Firebase Auth)
  firebase_auth:
    authorizationUrl: ""
    flow: "implicit"
    type: "oauth2"
    x-google-issuer: "https://securetoken.google.com/YOUR_PROJECT_ID"
    x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com"
    x-google-audiences: "YOUR_PROJECT_ID"

paths:
  /request/create:
    post:
      summary: Create a new guest request
      operationId: createServiceRequest
      x-google-backend:
        address: https://REGION-PROJECT_ID.cloudfunctions.net/createServiceRequest
      security:
        - api_key: []
        - firebase_auth: []
      responses:
        '200':
          description: A successful response
  /request/update:
    put:
      summary: Update request status (Staff)
      operationId: updateRequestStatus
      x-google-backend:
        address: https://REGION-PROJECT_ID.cloudfunctions.net/updateRequestStatus
      security:
        - api_key: []
        - firebase_auth: []
      responses:
        '200':
          description: A successful response

Deployment Command

gcloud api-gateway api-configs create concierge-config \
  --api=concierge-api --openapi-spec=openapi2-functions.yaml \
  --project=YOUR_PROJECT_ID --backend-auth-service-account=YOUR_SA_EMAIL

4. Flutter Mobile Application Code

This implementation uses the Provider pattern for state management and Firestore for real-time updates.

Dependencies (pubspec.yaml) flutter, firebase_core, firebase_auth, cloud_firestore, http, provider
File: main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(HotelConciergeApp());
}

// 5. COLOR SCHEME [9]
// Hospitality Theme: Navy/Gold + Urgent Indicators
class AppColors {
  static const primary = Color(0xFF1A237E); // Navy Blue
  static const accent = Color(0xFFFFD700);  // Gold
  static const urgentRed = Color(0xFFD32F2F); // > 30 mins [9]
  static const safeGreen = Color(0xFF388E3C); // < 10 mins [9]
}

class HotelConciergeApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Digital Concierge',
      theme: ThemeData(
        primaryColor: AppColors.primary,
        scaffoldBackgroundColor: Colors.grey,
        appBarTheme: AppBarTheme(backgroundColor: AppColors.primary),
      ),
      home: LoginScreen(),
    );
  }
}

// 7. LOGIN SCREEN (Handled by Firebase Auth)
class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Future<void> _login() async {
    try {
      UserCredential user = await _auth.signInWithEmailAndPassword(
          email: _emailController.text, password: _passwordController.text);
      
      // Fetch Role from Firestore Users collection [2]
      DocumentSnapshot userDoc = await FirebaseFirestore.instance
          .collection('users').doc(user.user!.uid).get();
      
      if (userDoc['role'] == 'STAFF') {
        Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => StaffDashboard()));
      } else {
        Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => GuestDashboard()));
      }
    } catch (e) {
      print(e); // Handle error
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("Hotel Concierge Login", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.primary)),
            TextField(controller: _emailController, decoration: InputDecoration(labelText: "Email")),
            TextField(controller: _passwordController, decoration: InputDecoration(labelText: "Password"), obscureText: true),
            SizedBox(height: 20),
            ElevatedButton(onPressed: _login, child: Text("Login"), style: ElevatedButton.styleFrom(primary: AppColors.primary)),
          ],
        ),
      ),
    );
  }
}

// I. GUEST VIEW [10], [3]
class GuestDashboard extends StatelessWidget {
  final String userId = FirebaseAuth.instance.currentUser!.uid;

  // Function to call API Gateway
  Future<void> _submitRequest(String type, Map<String, dynamic> details) async {
    // Call API Gateway URL configured in Step 3
    // Payload: { guest_id: userId, service_type: type, details: details }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Welcome, Guest")),
      body: Column(
        children: [
          // Service Tiles [3]
          Expanded(
            child: GridView.count(
              crossAxisCount: 2,
              children: [
                _buildServiceTile(context, "Housekeeping", Icons.cleaning_services, 
                  () => _showRequestDialog(context, "HOUSEKEEPING")),
                _buildServiceTile(context, "Dining", Icons.restaurant, () {}),
                _buildServiceTile(context, "Transport", Icons.local_taxi, () {}),
                _buildServiceTile(context, "Front Desk", Icons.concierge_key, () {}),
              ],
            ),
          ),
          // My Activity (Real-time Status) [8]
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: FirebaseFirestore.instance.collection('master_requests')
                  .where('guest_id', isEqualTo: userId).snapshots(),
              builder: (context, snapshot) {
                if (!snapshot.hasData) return CircularProgressIndicator();
                return ListView(
                  children: snapshot.data!.docs.map((doc) {
                    return ListTile(
                      title: Text(doc['service_type']),
                      subtitle: Text("Status: ${doc['status']}"), // Updates PENDING -> COMPLETED
                      trailing: _buildStatusBadge(doc['status']),
                    );
                  }).toList(),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildServiceTile(BuildContext context, String title, IconData icon, VoidCallback onTap) {
    return Card(
      child: InkWell(
        onTap: onTap,
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(icon, size: 40, color: AppColors.primary), Text(title)]),
      ),
    );
  }

  Widget _buildStatusBadge(String status) {
    Color color = status == 'COMPLETED' ? AppColors.safeGreen : Colors.orange;
    return Chip(label: Text(status), backgroundColor: color);
  }

  // Example Housekeeping Dialog [11]
  void _showRequestDialog(BuildContext context, String type) {
    showDialog(context: context, builder: (context) {
      return AlertDialog(
        title: Text("Request Towels"),
        content: Column(mainAxisSize: MainAxisSize.min, children: [
          Text("Quantity: 2"), // Simplified for brevity
          TextField(decoration: InputDecoration(labelText: "Allergy Note")) // [6]
        ]),
        actions: [
          TextButton(
            child: Text("Submit"),
            onPressed: () {
              _submitRequest(type, {'item_name': 'TOWELS', 'quantity': 2});
              Navigator.pop(context);
            },
          )
        ],
      );
    });
  }
}

// II. SERVICE PROVIDER VIEW [9], [12]
class StaffDashboard extends StatelessWidget {
  final String dept = 'HOUSEKEEPING'; // In real app, get from User Claims

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Staff Dashboard - $dept")),
      body: StreamBuilder<QuerySnapshot>(
        // Filter: Status != COMPLETED [12]
        stream: FirebaseFirestore.instance.collection('master_requests')
            .where('service_type', isEqualTo: dept)
            .where('status', isNotEqualTo: 'COMPLETED')
            .snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) return CircularProgressIndicator();
          return ListView(
            children: snapshot.data!.docs.map((doc) {
              // Urgency Logic: Red if older than 30 mins [9]
              Timestamp created = doc['created_at'];
              bool isUrgent = DateTime.now().difference(created.toDate()).inMinutes > 30;
              
              return Card(
                color: isUrgent ? AppColors.urgentRed.withOpacity(0.1) : Colors.white,
                child: ListTile(
                  leading: Icon(Icons.room_service, color: isUrgent ? AppColors.urgentRed : AppColors.safeGreen),
                  title: Text("Req #${doc.id.substring(0,4)}"),
                  subtitle: Text(doc['status']),
                  trailing: ElevatedButton(
                    child: Text(doc['status'] == 'PENDING' ? "Accept" : "Complete"),
                    onPressed: () {
                      // Call API Gateway to update status [4]
                      // logic: if PENDING -> ASSIGNED, if ASSIGNED -> COMPLETED
                    },
                  ),
                ),
              );
            }).toList(),
          );
        },
      ),
    );
  }
}