Scheduler.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import cron from 'node-cron';
  2. import axios from 'axios';
  3. import dayjs from 'dayjs';
  4. import prisma from '../prisma/PrismaClient';
  5. import { URLSearchParams } from 'url';
  6. interface KeycloakUser {
  7. id: string;
  8. firstName?: string;
  9. lastName?: string;
  10. createdTimestamp?: number;
  11. attributes?: {
  12. phone?: string[];
  13. };
  14. }
  15. interface KeycloakRole {
  16. id: string;
  17. name: string;
  18. description?: string;
  19. composite?: boolean;
  20. clientRole?: boolean;
  21. containerId?: string;
  22. }
  23. const getAdminToken = async (): Promise<string> => {
  24. const response = await axios.post(
  25. `${process.env.KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
  26. new URLSearchParams({
  27. client_id: 'admin-cli',
  28. username: process.env.KEYCLOAK_ADMIN_USERNAME ?? '',
  29. password: process.env.KEYCLOAK_ADMIN_PASSWORD ?? '',
  30. grant_type: 'password',
  31. }),
  32. {
  33. headers: {
  34. 'Content-Type': 'application/x-www-form-urlencoded',
  35. },
  36. }
  37. );
  38. return response.data.access_token;
  39. };
  40. const getAllUsers = async (token: string): Promise<KeycloakUser[]> => {
  41. const res = await axios.get(
  42. `${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`,
  43. {
  44. headers: {
  45. Authorization: `Bearer ${token}`,
  46. },
  47. }
  48. );
  49. return res.data;
  50. };
  51. const getUserRoles = async (token: string, userId: string): Promise<KeycloakRole[]> => {
  52. const res = await axios.get(
  53. `${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings/realm`,
  54. {
  55. headers: {
  56. Authorization: `Bearer ${token}`,
  57. },
  58. }
  59. );
  60. return res.data;
  61. };
  62. const syncUserToDB = async (user: KeycloakUser, token: string): Promise<void> => {
  63. const fullname = `${user.firstName || ''} ${user.lastName || ''}`.trim();
  64. const phone = user.attributes?.phone?.[0] || null;
  65. const roles = await getUserRoles(token, user.id);
  66. const role = roles[0]?.name || null;
  67. const existing = await prisma.userKeycloak.findUnique({
  68. where: { id: user.id },
  69. });
  70. if (!existing) {
  71. await prisma.userKeycloak.create({
  72. data: {
  73. id: user.id,
  74. fullname,
  75. phone,
  76. role
  77. },
  78. });
  79. console.log('✔️ Synced user:', fullname);
  80. }
  81. };
  82. // Cron job setiap 1 menit
  83. cron.schedule('* * * * *', async () => {
  84. console.log('[SYNC] Checking for new users...');
  85. try {
  86. const token = await getAdminToken();
  87. const users = await getAllUsers(token);
  88. const now = dayjs();
  89. const oneMinuteAgo = now.subtract(1, 'minute');
  90. const newUsers = users.filter(
  91. (u) =>
  92. u.createdTimestamp &&
  93. dayjs(Number(u.createdTimestamp)).isAfter(oneMinuteAgo)
  94. );
  95. for (const user of newUsers) {
  96. await syncUserToDB(user, token);
  97. }
  98. if (newUsers.length === 0) {
  99. console.log('ℹ️ No new users.');
  100. }
  101. } catch (err: any) {
  102. console.error('❌ Sync error:', err.message);
  103. }
  104. });