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).
Create Project
Go to the GCP Console and create a new project.
Enable Firebase
Navigate to "Firebase" in the menu and link your GCP project.
Enable Authentication
Go to Firebase Console > Authentication > Sign-in method. Enable Email/Password (as the primary method for guests and staff).
Database Setup
Go to Firestore Database and click "Create Database". Start in Production Mode and apply the Security Rules.
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".
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.
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.
flutter, firebase_core, firebase_auth, cloud_firestore, http, provider
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(),
);
},
),
);
}
}