academe-kit 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
+ var axios = require('axios');
5
6
 
6
7
  function _interopNamespaceDefault(e) {
7
8
  var n = Object.create(null);
@@ -2250,66 +2251,250 @@ function isObject(input) {
2250
2251
  return typeof input === 'object' && input !== null;
2251
2252
  }
2252
2253
 
2253
- const AcademeAuthProvider = ({ realm, hubUrl, children, clientId, keycloakUrl, }) => {
2254
- return (jsxRuntime.jsx(ReactKeycloakProvider, { authClient: new Keycloak({ clientId, realm, url: keycloakUrl }), children: jsxRuntime.jsx(SecurityProvider, { hubUrl: hubUrl, children: children }) }));
2254
+ /**
2255
+ * User Service - Fetches complete user data from MongoDB
2256
+ * This service interacts with the new unified users collection
2257
+ */
2258
+ class UserService {
2259
+ constructor(config) {
2260
+ this.cachedUser = null;
2261
+ this.cacheExpiry = 0;
2262
+ this.getAccessToken = config.getAccessToken;
2263
+ this.client = axios.create({
2264
+ baseURL: config.apiBaseUrl,
2265
+ headers: {
2266
+ 'Content-Type': 'application/json'
2267
+ }
2268
+ });
2269
+ // Add auth interceptor
2270
+ this.client.interceptors.request.use((config) => {
2271
+ const token = this.getAccessToken();
2272
+ if (token) {
2273
+ config.headers.Authorization = `Bearer ${token}`;
2274
+ }
2275
+ return config;
2276
+ });
2277
+ // Add response interceptor for error handling
2278
+ this.client.interceptors.response.use((response) => response, (error) => {
2279
+ if (error.response?.status === 401) {
2280
+ // Clear cache on authentication error
2281
+ this.clearCache();
2282
+ }
2283
+ return Promise.reject(error);
2284
+ });
2285
+ }
2286
+ /**
2287
+ * Get current user data from MongoDB
2288
+ * Includes caching to reduce API calls
2289
+ */
2290
+ async getCurrentUser(forceRefresh = false) {
2291
+ try {
2292
+ // Check cache
2293
+ if (!forceRefresh && this.cachedUser && Date.now() < this.cacheExpiry) {
2294
+ return this.cachedUser;
2295
+ }
2296
+ const response = await this.client.get('/api/users/me');
2297
+ if (response.data) {
2298
+ // Cache for 5 minutes
2299
+ this.cachedUser = response.data;
2300
+ this.cacheExpiry = Date.now() + (5 * 60 * 1000);
2301
+ return response.data;
2302
+ }
2303
+ return null;
2304
+ }
2305
+ catch (error) {
2306
+ console.error('Error fetching current user:', error);
2307
+ // If it's a 404, user doesn't exist in MongoDB yet
2308
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
2309
+ // User will be auto-created on next API call
2310
+ return null;
2311
+ }
2312
+ throw error;
2313
+ }
2314
+ }
2315
+ /**
2316
+ * Update current user data
2317
+ */
2318
+ async updateCurrentUser(data) {
2319
+ try {
2320
+ // First get current user to get the ID
2321
+ const currentUser = await this.getCurrentUser();
2322
+ if (!currentUser) {
2323
+ throw new Error('User not found');
2324
+ }
2325
+ const response = await this.client.put(`/api/users/${currentUser.id}`, data);
2326
+ if (response.data) {
2327
+ // Update cache
2328
+ this.cachedUser = response.data;
2329
+ this.cacheExpiry = Date.now() + (5 * 60 * 1000);
2330
+ return response.data;
2331
+ }
2332
+ return null;
2333
+ }
2334
+ catch (error) {
2335
+ console.error('Error updating user:', error);
2336
+ throw error;
2337
+ }
2338
+ }
2339
+ /**
2340
+ * Clear cached user data
2341
+ */
2342
+ clearCache() {
2343
+ this.cachedUser = null;
2344
+ this.cacheExpiry = 0;
2345
+ }
2346
+ /**
2347
+ * Check if user has a specific role
2348
+ */
2349
+ hasRole(user, role) {
2350
+ if (!user || !user.roles)
2351
+ return false;
2352
+ return user.roles.includes(role);
2353
+ }
2354
+ /**
2355
+ * Check if user has access to a specific school
2356
+ */
2357
+ hasSchoolAccess(user, schoolId) {
2358
+ if (!user)
2359
+ return false;
2360
+ // Admin has access to all schools
2361
+ if (this.hasRole(user, 'admin_academe'))
2362
+ return true;
2363
+ // Check if user's school matches
2364
+ return user.school_id === schoolId;
2365
+ }
2366
+ /**
2367
+ * Check if user is a student (has student-specific data)
2368
+ */
2369
+ isStudent(user) {
2370
+ if (!user)
2371
+ return false;
2372
+ return !!user.registration_number || this.hasRole(user, 'student');
2373
+ }
2374
+ /**
2375
+ * Check if user has completed onboarding
2376
+ */
2377
+ hasCompletedOnboarding(user) {
2378
+ if (!user)
2379
+ return false;
2380
+ return user.onboarding_completo;
2381
+ }
2382
+ /**
2383
+ * Check if user has access enabled
2384
+ */
2385
+ hasAccessEnabled(user) {
2386
+ if (!user)
2387
+ return false;
2388
+ return user.acesso_liberado && user.active;
2389
+ }
2390
+ }
2391
+ // Export singleton instance creator
2392
+ const createUserService = (config) => {
2393
+ return new UserService(config);
2394
+ };
2395
+
2396
+ const AcademeAuthProvider = ({ realm, hubUrl, children, clientId, keycloakUrl, apiBaseUrl, }) => {
2397
+ return (jsxRuntime.jsx(ReactKeycloakProvider, { authClient: new Keycloak({ clientId, realm, url: keycloakUrl }), children: jsxRuntime.jsx(SecurityProvider, { hubUrl: hubUrl, apiBaseUrl: apiBaseUrl, children: children }) }));
2255
2398
  };
2256
2399
  const SecurityContext = React.createContext({
2257
2400
  isInitialized: false,
2258
- getUser: () => ({
2259
- email: "",
2260
- name: "",
2261
- lastName: "",
2262
- attributes: {
2263
- school_ids: [],
2264
- },
2265
- }),
2401
+ user: null,
2402
+ refreshUserData: async () => { },
2266
2403
  signOut: () => null,
2267
2404
  goToLogin: () => null,
2268
2405
  hasSchool: () => false,
2269
2406
  hasRealmRole: () => false,
2270
- redirectToHub: () => null,
2271
2407
  hasClientRole: () => false,
2272
2408
  isAuthenticated: () => false,
2273
2409
  });
2274
- const SecurityProvider = ({ hubUrl, children, }) => {
2410
+ const SecurityProvider = ({ apiBaseUrl = "https://api.academe.com.br", children, }) => {
2275
2411
  const [isInitialized, setIsInitialized] = React.useState(false);
2412
+ const [currentUser, setCurrentUser] = React.useState(null);
2276
2413
  const { initialized, keycloak } = useKeycloak();
2414
+ const userService = createUserService({
2415
+ apiBaseUrl,
2416
+ getAccessToken: () => keycloak?.token || null,
2417
+ });
2277
2418
  React.useEffect(() => {
2278
2419
  setIsInitialized(initialized);
2279
2420
  }, [initialized]);
2280
2421
  React.useEffect(() => {
2281
2422
  window.accessToken = keycloak.token;
2282
2423
  }, [keycloak.token]);
2283
- const getUser = () => {
2424
+ // Fetch user data from MongoDB when authenticated
2425
+ React.useEffect(() => {
2426
+ const fetchUserData = async () => {
2427
+ if (initialized && keycloak?.authenticated && keycloak?.token) {
2428
+ try {
2429
+ const mongoUser = await userService.getCurrentUser();
2430
+ if (mongoUser) {
2431
+ const academeUser = {
2432
+ ...mongoUser,
2433
+ keycloakUser: getKeycloakUser(),
2434
+ };
2435
+ setCurrentUser(academeUser);
2436
+ }
2437
+ }
2438
+ catch (error) {
2439
+ console.error("Error fetching user data:", error);
2440
+ }
2441
+ }
2442
+ else if (!keycloak?.authenticated) {
2443
+ // Clear user data when not authenticated
2444
+ setCurrentUser(null);
2445
+ userService.clearCache();
2446
+ }
2447
+ };
2448
+ fetchUserData();
2449
+ }, [initialized, keycloak?.authenticated, keycloak?.token]);
2450
+ const getKeycloakUser = () => {
2284
2451
  const idTokenParsed = keycloak?.idTokenParsed || {};
2285
2452
  return {
2286
- email: keycloak?.idTokenParsed?.email || "",
2287
- name: keycloak?.idTokenParsed?.given_name || "",
2288
- lastName: keycloak?.idTokenParsed?.family_name || "",
2289
- attributes: {
2290
- school_ids: (idTokenParsed.school_ids || []),
2291
- },
2453
+ email: idTokenParsed?.email || "",
2454
+ name: idTokenParsed?.given_name || "",
2455
+ lastName: idTokenParsed?.family_name || "",
2292
2456
  };
2293
2457
  };
2294
- const redirectToHub = (errorMessage) => {
2295
- window.location.replace(`${hubUrl}?errorMessage=${errorMessage}`);
2296
- };
2458
+ const refreshUserData = React.useCallback(async () => {
2459
+ if (keycloak?.authenticated) {
2460
+ try {
2461
+ const mongoUser = await userService.getCurrentUser(true); // Force refresh
2462
+ if (mongoUser) {
2463
+ const academeUser = {
2464
+ ...mongoUser,
2465
+ keycloakUser: getKeycloakUser(),
2466
+ };
2467
+ setCurrentUser(academeUser);
2468
+ }
2469
+ }
2470
+ catch (error) {
2471
+ console.error("Error refreshing user data:", error);
2472
+ }
2473
+ }
2474
+ }, [keycloak?.authenticated]);
2297
2475
  const signOut = () => {
2476
+ userService.clearCache();
2477
+ setCurrentUser(null);
2298
2478
  keycloak?.logout();
2299
2479
  };
2300
2480
  const isAuthenticated = () => {
2301
2481
  return (!!keycloak?.idTokenParsed?.email?.length && !!keycloak?.authenticated);
2302
2482
  };
2303
2483
  const hasSchool = (schoolId) => {
2304
- return getUser().attributes.school_ids.includes(schoolId);
2484
+ if (currentUser?.school_id) {
2485
+ return (currentUser.school_id === schoolId ||
2486
+ userService.hasRole(currentUser, "admin_academe"));
2487
+ }
2488
+ // Fallback to Keycloak data
2489
+ return false; // No school_id in Keycloak data
2305
2490
  };
2306
2491
  return (jsxRuntime.jsx(SecurityContext.Provider, { value: {
2307
2492
  isInitialized,
2308
- getUser,
2493
+ user: currentUser,
2494
+ refreshUserData,
2309
2495
  signOut,
2310
- redirectToHub,
2311
2496
  isAuthenticated,
2312
- hasSchool: hasSchool,
2497
+ hasSchool,
2313
2498
  goToLogin: keycloak.login,
2314
2499
  hasRealmRole: keycloak?.hasRealmRole,
2315
2500
  hasClientRole: keycloak.hasResourceRole,
@@ -5400,8 +5585,8 @@ function Spinner({ className, ...props }) {
5400
5585
  return (jsxRuntime.jsx(LoaderCircle, { role: "status", "aria-label": "Loading", className: cn("size-10 animate-spin text-violet-500", className), ...props }));
5401
5586
  }
5402
5587
 
5403
- const ProtectedApp = ({ children, unauthorizedMessage, requiredClientRoles, }) => {
5404
- const { isInitialized, goToLogin, hasClientRole, redirectToHub, isAuthenticated, } = useAcademeAuth();
5588
+ const ProtectedApp = ({ children, requiredClientRoles, }) => {
5589
+ const { isInitialized, goToLogin, hasClientRole, isAuthenticated } = useAcademeAuth();
5405
5590
  if (!isInitialized) {
5406
5591
  return (jsxRuntime.jsx("div", { className: "flex w-screen h-screen items-center justify-center", children: jsxRuntime.jsx(Spinner, { className: "size-10 text-violet-500" }) }));
5407
5592
  }
@@ -5411,7 +5596,6 @@ const ProtectedApp = ({ children, unauthorizedMessage, requiredClientRoles, }) =
5411
5596
  }
5412
5597
  if (requiredClientRoles &&
5413
5598
  !requiredClientRoles?.some((role) => hasClientRole(role))) {
5414
- redirectToHub(unauthorizedMessage || "Unauthorized");
5415
5599
  return jsxRuntime.jsx(jsxRuntime.Fragment, {});
5416
5600
  }
5417
5601
  return children;
@@ -5479,5 +5663,6 @@ exports.ProtectedComponent = ProtectedComponent;
5479
5663
  exports.ProtectedRouter = ProtectedRouter;
5480
5664
  exports.Spinner = Spinner;
5481
5665
  exports.cn = cn;
5666
+ exports.createUserService = createUserService;
5482
5667
  exports.useAcademeAuth = useAcademeAuth;
5483
5668
  //# sourceMappingURL=index.js.map