Skip to content

mohammedzom/HR-System-API

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 

Repository files navigation

Flutter Integration Guide for HR System API

Comprehensive guide for Flutter developers to integrate with HR System API


🌐 API Endpoints Structure

Base URL

Production: https://api.mohammedzom.online/api

Endpoint Structure Overview

πŸ“¦ API Root (/api)
β”‚
β”œβ”€β”€ πŸ” Authentication Required: API Key (x-api-key header)
β”‚
β”œβ”€β”¬β”€ πŸ“‚ /auth (Authentication & User Management)
β”‚ β”‚
β”‚ β”œβ”€β”€ πŸ†“ Public Endpoints (No Bearer Token Required)
β”‚ β”‚   β”œβ”€β”€ POST /auth/register
β”‚ β”‚   β”œβ”€β”€ POST /auth/verify-otp
β”‚ β”‚   β”œβ”€β”€ POST /auth/login
β”‚ β”‚   β”œβ”€β”€ POST /auth/forgot-password
β”‚ β”‚   └── POST /auth/verify-forgot-password-otp
β”‚ β”‚
β”‚ └── πŸ”’ Protected Endpoints (Bearer Token Required)
β”‚     β”œβ”€β”€ POST /auth/logout
β”‚     β”œβ”€β”€ POST /auth/logout-all
β”‚     └── DELETE /auth/delete-account
β”‚
└─── πŸ”’ User Endpoints (Bearer Token Required)
      └── GET /user (Get current user profile)

Complete Endpoints List

Method Endpoint Auth Level Description
Authentication - Public
POST /auth/register API Key Only Create new account
POST /auth/verify-otp API Key Only Verify OTP code
POST /auth/login API Key Only User login
POST /auth/forgot-password API Key Only Request password reset OTP
POST /auth/verify-forgot-password-otp API Key Only Reset password with OTP
Authentication - Protected
POST /auth/logout API Key + Token Logout from current device
POST /auth/logout-all API Key + Token Logout from all devices
DELETE /auth/delete-account API Key + Token Delete user account
User Management
GET /user API Key + Token Get current user profile

Security Layers

πŸ”’ Layer 1: API Key (Required for ALL requests)
   └── Header: x-api-key: {your-api-key}

πŸ”’ Layer 2: Bearer Token (Required for protected endpoints)
   └── Header: Authorization: Bearer {access_token}

οΏ½ Quick API Reference

1. POST /auth/register

Purpose: Create a new user account

Request:

{
    "email": "user@example.com",
    "password": "password123",
    "login_type": "email"
}

Response (201):

{
    "success": true,
    "message": "Account created successfully. Please verify your OTP code.",
    "data": {
        "user_id": 1,
        "identifier": "user@example.com",
        "login_type": "email",
        "otp_type": "registration",
        "dev_otp": "123456"
    }
}

2. POST /auth/verify-otp

Purpose: Verify OTP and activate account

Request:

{
    "identifier": "user@example.com",
    "code": "123456",
    "type": "registration"
}

Response (200):

{
    "success": true,
    "message": "OTP verified successfully.",
    "data": {
        "user": { "id": 1, "email": "user@example.com", "is_active": true },
        "access_token": "1|xxxxx",
        "token_type": "Bearer"
    }
}

3. POST /auth/login

Purpose: Authenticate user

Request:

{
    "identifier": "user@example.com",
    "password": "password123",
    "login_type": "email"
}

Response (200):

{
    "success": true,
    "message": "Login successful.",
    "data": {
        "user": { "id": 1, "email": "user@example.com" },
        "access_token": "1|xxxxx",
        "token_type": "Bearer"
    }
}

4. POST /auth/forgot-password

Purpose: Request password reset OTP

Request:

{
    "identifier": "user@example.com",
    "type": "forget_password",
    "login_type": "email"
}

Response (200):

{
    "success": true,
    "message": "OTP code sent successfully.",
    "data": {
        "identifier": "user@example.com",
        "otp_type": "forget_password",
        "dev_otp": "123456"
    }
}

5. POST /auth/verify-forgot-password-otp

Purpose: Reset password with OTP

Request:

{
    "identifier": "user@example.com",
    "code": "123456",
    "password": "newpassword123",
    "password_confirmation": "newpassword123"
}

Response (200):

{
    "success": true,
    "message": "Password has been reset successfully.",
    "data": {
        "user": { "id": 1, "email": "user@example.com" },
        "access_token": "1|xxxxx",
        "token_type": "Bearer"
    }
}

6. POST /auth/logout πŸ”’

Purpose: Logout from current device
Headers: Authorization: Bearer {token}

Response (200):

{
    "success": true,
    "message": "Logout successful.",
    "data": null
}

7. POST /auth/logout-all πŸ”’

Purpose: Logout from all devices
Headers: Authorization: Bearer {token}

Response (200):

{
    "success": true,
    "message": "Logged out from all devices successfully.",
    "data": null
}

8. DELETE /auth/delete-account πŸ”’

Purpose: Permanently delete user account
Headers: Authorization: Bearer {token}

Response (200):

{
    "success": true,
    "message": "Account has been deleted successfully.",
    "data": null
}

9. GET /user πŸ”’

Purpose: Get current user profile
Headers: Authorization: Bearer {token}

Response (200):

{
    "success": true,
    "message": "User retrieved successfully.",
    "data": {
        "id": 1,
        "email": "user@example.com",
        "phone": null,
        "is_active": true,
        "role": "employee"
    }
}

οΏ½πŸ“‹ Table of Contents

  1. API Endpoints Structure
  2. Requirements
  3. Initial Setup
  4. Creating API Service
  5. Authentication
  6. Usage Examples
  7. Error Handling
  8. Best Practices

Requirements

Required Dependencies

Add these packages to your pubspec.yaml:

dependencies:
    flutter:
        sdk: flutter

    # HTTP Client
    dio: ^5.4.0

    # State Management (optional)
    provider: ^6.1.1

    # Secure Storage for tokens
    flutter_secure_storage: ^9.0.0

    # JSON Serialization
    json_annotation: ^4.8.1

dev_dependencies:
    build_runner: ^2.4.7
    json_serializable: ^6.7.1

Then run:

flutter pub get

Initial Setup

1. API Configuration

Create a file lib/core/config/api_config.dart:

class ApiConfig {
  // Base URLs
  static const String productionUrl = 'https://api.mohammedzom.online';
  static const String localUrl = 'http://localhost:8000';

  // Current environment
  static const String baseUrl = productionUrl; // Change based on environment

  // API Key (IMPORTANT!)
  static const String apiKey = '';

  // Endpoints
  static const String loginEndpoint = '/api/auth/login';
  static const String registerEndpoint = '/api/auth/register';
  static const String logoutEndpoint = '/api/auth/logout';
  static const String profileEndpoint = '/api/user/profile';
  static const String updateProfileEndpoint = '/api/user/profile';

  // Timeouts
  static const Duration connectionTimeout = Duration(seconds: 30);
  static const Duration receiveTimeout = Duration(seconds: 30);
}

2. API Response Models

Create a file lib/core/models/api_response.dart:

class ApiResponse<T> {
  final bool success;
  final String message;
  final T? data;
  final String? errorCode;

  ApiResponse({
    required this.success,
    required this.message,
    this.data,
    this.errorCode,
  });

  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T Function(dynamic)? fromJsonT,
  ) {
    return ApiResponse<T>(
      success: json['success'] ?? false,
      message: json['message'] ?? '',
      data: json['data'] != null && fromJsonT != null
          ? fromJsonT(json['data'])
          : json['data'],
      errorCode: json['error_code'],
    );
  }
}

Creating API Service

1. Dio Client Setup

Create a file lib/core/services/dio_client.dart:

import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../config/api_config.dart';

class DioClient {
  static final DioClient _instance = DioClient._internal();
  factory DioClient() => _instance;

  late Dio _dio;
  final _storage = const FlutterSecureStorage();

  DioClient._internal() {
    _dio = Dio(
      BaseOptions(
        baseUrl: ApiConfig.baseUrl,
        connectTimeout: ApiConfig.connectionTimeout,
        receiveTimeout: ApiConfig.receiveTimeout,
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
          // API Key header (IMPORTANT!)
          'x-api-key': ApiConfig.apiKey,
        },
      ),
    );

    // Add interceptors
    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) async {
          // Add Bearer token if exists
          final token = await _storage.read(key: 'auth_token');
          if (token != null) {
            options.headers['Authorization'] = 'Bearer $token';
          }

          print('πŸš€ Request: \${options.method} \${options.path}');
          print('πŸ“¦ Data: \${options.data}');

          return handler.next(options);
        },
        onResponse: (response, handler) {
          print('βœ… Response: \${response.statusCode}');
          return handler.next(response);
        },
        onError: (error, handler) async {
          print('❌ Error: \${error.response?.statusCode}');

          // Handle 401 Unauthorized
          if (error.response?.statusCode == 401) {
            await _storage.delete(key: 'auth_token');
          }

          return handler.next(error);
        },
      ),
    );
  }

  Dio get dio => _dio;

  Future<void> saveToken(String token) async {
    await _storage.write(key: 'auth_token', value: token);
  }

  Future<String?> getToken() async {
    return await _storage.read(key: 'auth_token');
  }

  Future<void> clearToken() async {
    await _storage.delete(key: 'auth_token');
  }
}

2. Auth Service

Create a file lib/core/services/auth_service.dart:

import 'package:dio/dio.dart';
import '../config/api_config.dart';
import '../models/api_response.dart';
import 'dio_client.dart';

class AuthService {
  final DioClient _dioClient = DioClient();

  /// Login
  Future<ApiResponse<Map<String, dynamic>>> login({
    required String identifier, // email or phone
    required String password,
    required String loginType, // 'email' or 'phone'
    String? fcmToken,
  }) async {
    try {
      final response = await _dioClient.dio.post(
        ApiConfig.loginEndpoint,
        data: {
          'identifier': identifier,
          'password': password,
          'login_type': loginType,
          if (fcmToken != null) 'fcm_token': fcmToken,
        },
      );

      final apiResponse = ApiResponse<Map<String, dynamic>>.fromJson(
        response.data,
        (data) => data as Map<String, dynamic>,
      );

      // Save token if success
      if (apiResponse.success && apiResponse.data?['token'] != null) {
        await _dioClient.saveToken(apiResponse.data!['token']);
      }

      return apiResponse;
    } on DioException catch (e) {
      return ApiResponse(
        success: false,
        message: e.response?.data['message'] ?? 'Network error',
        errorCode: e.response?.data['error_code'],
      );
    }
  }

  /// Get user profile
  Future<ApiResponse<Map<String, dynamic>>> getProfile() async {
    try {
      final response = await _dioClient.dio.get(
        ApiConfig.profileEndpoint,
      );

      return ApiResponse<Map<String, dynamic>>.fromJson(
        response.data,
        (data) => data as Map<String, dynamic>,
      );
    } on DioException catch (e) {
      return ApiResponse(
        success: false,
        message: e.response?.data['message'] ?? 'Network error',
        errorCode: e.response?.data['error_code'],
      );
    }
  }

  /// Logout
  Future<ApiResponse<void>> logout() async {
    try {
      final response = await _dioClient.dio.post(
        ApiConfig.logoutEndpoint,
      );

      await _dioClient.clearToken();

      return ApiResponse<void>.fromJson(response.data, null);
    } on DioException catch (e) {
      return ApiResponse(
        success: false,
        message: e.response?.data['message'] ?? 'Network error',
        errorCode: e.response?.data['error_code'],
      );
    }
  }
}

Authentication

Login Example

Complete login screen example:

import 'package:flutter/material.dart';
import '../core/services/auth_service.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({Key? key}) : super(key: key);

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _authService = AuthService();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isLoading = false;

  Future<void> _handleLogin() async {
    setState(() => _isLoading = true);

    try {
      final response = await _authService.login(
        identifier: _emailController.text.trim(),
        password: _passwordController.text,
        loginType: 'email',
      );

      if (response.success) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Welcome!'),
              backgroundColor: Colors.green,
            ),
          );

          Navigator.pushReplacementNamed(context, '/home');
        }
      } else {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(response.message),
              backgroundColor: Colors.red,
            ),
          );
        }
      }
    } finally {
      if (mounted) {
        setState(() => _isLoading = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: const InputDecoration(
                labelText: 'Email',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _passwordController,
              decoration: const InputDecoration(
                labelText: 'Password',
                border: OutlineInputBorder(),
              ),
              obscureText: true,
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              height: 50,
              child: ElevatedButton(
                onPressed: _isLoading ? null : _handleLogin,
                child: _isLoading
                    ? const CircularProgressIndicator(color: Colors.white)
                    : const Text('Login'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Error Handling

Error Codes

Handle API error codes:

class ApiErrorHandler {
  static String getErrorMessage(String? errorCode) {
    switch (errorCode) {
      case 'INVALID_API_KEY':
        return 'Invalid API key';
      case 'UNAUTHENTICATED':
        return 'Please login';
      case 'INVALID_CREDENTIALS':
        return 'Invalid credentials';
      case 'MODEL_NOT_FOUND':
        return 'Not found';
      default:
        return 'An error occurred';
    }
  }
}

Best Practices

βœ… Checklist

  • Add Dio dependency
  • Create ApiConfig with API key
  • Create DioClient with interceptors
  • Create AuthService
  • Use secure storage for tokens
  • Handle errors properly
  • Test on production

Good luck! πŸš€

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published