Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions backend/cmd/clerk/sync.go → backend/cmd/cli/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"

"github.com/generate/selfserve/config"
"github.com/generate/selfserve/internal/models"
"github.com/generate/selfserve/internal/repository"
"github.com/generate/selfserve/internal/service/clerk"
storage "github.com/generate/selfserve/internal/service/storage/postgres"
Expand All @@ -26,8 +27,7 @@ func main() {
defer repo.Close()
usersRepo := repository.NewUsersRepository(repo.DB)

path := "/users"
err = syncUsers(ctx, cfg.BaseURL+path, cfg.SecretKey, usersRepo)
err = syncUsers(ctx, cfg.BaseURL, cfg.SecretKey, usersRepo)
if err != nil {
log.Fatal(err)
}
Expand All @@ -37,19 +37,34 @@ func main() {
func syncUsers(ctx context.Context, clerkBaseURL string, clerkSecret string,
usersRepo storage.UsersRepository) error {

users, err := clerk.FetchUsersFromClerk(clerkBaseURL, clerkSecret)
users, err := clerk.FetchUsersFromClerk(clerkBaseURL+"/users", clerkSecret);
if err != nil {
return err
return fmt.Errorf("failed to fetch users: %w", err);
}

transformed, err := clerk.ValidateAndReformatUserData(users)
if err != nil {
return err
}
for _, u := range users {
if len(u.OrganizationMemberships) == 0 {
log.Printf("skipping user %s: no org membership found", u.ID);
continue;
}

orgID := u.OrganizationMemberships[0].Organization.ID

if err := usersRepo.BulkInsertUsers(ctx, transformed); err != nil {
return fmt.Errorf("failed to insert users: %w", err)
createUser := &models.CreateUser{
ID: u.ID,
FirstName: u.FirstName,
LastName: u.LastName,
HotelID: orgID,
ProfilePicture: u.ImageUrl,
}

if _, err := usersRepo.InsertUser(ctx, createUser); err != nil {
log.Printf("failed to insert user %s: %v", u.ID, err);
continue;
}

log.Printf("inserted user %s", u.ID);
}

return nil
}
}
2 changes: 1 addition & 1 deletion backend/internal/models/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type CreateUser struct {
ID string `json:"id" validate:"notblank" example:"user_123"`
FirstName string `json:"first_name" validate:"notblank" example:"John"`
LastName string `json:"last_name" validate:"notblank" example:"Doe"`
HotelID string `json:"hotel_id" validate:"notblank" example:"org_550e8400-e29b-41d4-a716-446655440000"`
HotelID string `json:"hotel_id" validate:"notblank" example:"550e8400-e29b-41d4-a716-446655440000"`
EmployeeID *string `json:"employee_id,omitempty" validate:"omitempty" example:"EMP-1234"`
ProfilePicture *string `json:"profile_picture,omitempty" validate:"omitempty,url" example:"https://example.com/john.jpg"`
Role *string `json:"role,omitempty" validate:"omitempty" example:"Receptionist"`
Expand Down
2 changes: 1 addition & 1 deletion clients/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stack } from "expo-router";
import { Stack, Redirect } from "expo-router";

Check warning on line 1 in clients/mobile/app/_layout.tsx

View workflow job for this annotation

GitHub Actions / Lint

'Redirect' is defined but never used
import { StatusBar } from "expo-status-bar";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
Expand Down
11 changes: 11 additions & 0 deletions clients/mobile/app/no-org.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { View, Text } from "react-native";

export default function NoOrg() {
return (
<View className="flex-1 items-center justify-center px-[8vw] bg-white">
<Text className="text-[5vw] font-semibold text-black text-center mb-[2vh]">
No Organization Found
</Text>
</View>
);
}
8 changes: 4 additions & 4 deletions clients/shared/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { getConfig } from "./config";
export const createRequest = (
getToken: () => Promise<string | null>,
baseUrl: string,
hotelId: string,
) => {
const hardCodedHotelId = "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
return async <T>(config: RequestConfig): Promise<T> => {
let fullUrl = `${baseUrl}${config.url}`;
if (config.params && Object.keys(config.params).length > 0) {
Expand All @@ -24,7 +24,7 @@ export const createRequest = (
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Bearer ${token}` }),
"X-Hotel-ID": hardCodedHotelId,
"X-Hotel-ID": hotelId,
...config.headers,
},
body: config.data ? JSON.stringify(config.data) : undefined,
Expand Down Expand Up @@ -74,9 +74,9 @@ export const useAPIClient = (): HttpClient => {
// can be called during app startup (e.g. in a useEffect)
// before any API calls are executed.
const request = async <T>(config: RequestConfig): Promise<T> => {
const { getToken } = getConfig();
const { getToken, hotelId } = getConfig();
const baseUrl = getBaseUrl();
const doRequest = createRequest(getToken, baseUrl);
const doRequest = createRequest(getToken, baseUrl, hotelId);
return doRequest<T>(config);
};

Expand Down
4 changes: 2 additions & 2 deletions clients/shared/src/api/orval-mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { RequestConfig } from "../types/api.types";
export const useCustomInstance = <T>(): ((
config: RequestConfig,
) => Promise<T>) => {
const { getToken } = getConfig();
const request = createRequest(getToken, getBaseUrl());
const { getToken, hotelId } = getConfig();
const request = createRequest(getToken, getBaseUrl(), hotelId);

return async (config: RequestConfig): Promise<T> => {
const response = await request<T>(config);
Expand Down
21 changes: 21 additions & 0 deletions clients/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as SignUpRouteImport } from './routes/sign-up'
import { Route as SignInRouteImport } from './routes/sign-in'
import { Route as NoOrgRouteImport } from './routes/no-org'
import { Route as ProtectedRouteImport } from './routes/_protected'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ProtectedTestApiRouteImport } from './routes/_protected/test-api'
Expand All @@ -32,6 +33,11 @@ const SignInRoute = SignInRouteImport.update({
path: '/sign-in',
getParentRoute: () => rootRouteImport,
} as any)
const NoOrgRoute = NoOrgRouteImport.update({
id: '/no-org',
path: '/no-org',
getParentRoute: () => rootRouteImport,
} as any)
const ProtectedRoute = ProtectedRouteImport.update({
id: '/_protected',
getParentRoute: () => rootRouteImport,
Expand Down Expand Up @@ -84,6 +90,7 @@ const ProtectedGuestsGuestIdRoute = ProtectedGuestsGuestIdRouteImport.update({

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/no-org': typeof NoOrgRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/home': typeof ProtectedHomeRoute
Expand All @@ -97,6 +104,7 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/no-org': typeof NoOrgRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/home': typeof ProtectedHomeRoute
Expand All @@ -111,6 +119,7 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/_protected': typeof ProtectedRouteWithChildren
'/no-org': typeof NoOrgRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/_protected/home': typeof ProtectedHomeRoute
Expand All @@ -126,6 +135,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/no-org'
| '/sign-in'
| '/sign-up'
| '/home'
Expand All @@ -139,6 +149,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/no-org'
| '/sign-in'
| '/sign-up'
| '/home'
Expand All @@ -152,6 +163,7 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/_protected'
| '/no-org'
| '/sign-in'
| '/sign-up'
| '/_protected/home'
Expand All @@ -167,6 +179,7 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ProtectedRoute: typeof ProtectedRouteWithChildren
NoOrgRoute: typeof NoOrgRoute
SignInRoute: typeof SignInRoute
SignUpRoute: typeof SignUpRoute
}
Expand All @@ -187,6 +200,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SignInRouteImport
parentRoute: typeof rootRouteImport
}
'/no-org': {
id: '/no-org'
path: '/no-org'
fullPath: '/no-org'
preLoaderRoute: typeof NoOrgRouteImport
parentRoute: typeof rootRouteImport
}
'/_protected': {
id: '/_protected'
path: ''
Expand Down Expand Up @@ -299,6 +319,7 @@ const ProtectedRouteWithChildren = ProtectedRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ProtectedRoute: ProtectedRouteWithChildren,
NoOrgRoute: NoOrgRoute,
SignInRoute: SignInRoute,
SignUpRoute: SignUpRoute,
}
Expand Down
6 changes: 5 additions & 1 deletion clients/web/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router";
import {
HeadContent,
Scripts,
createRootRoute
} from "@tanstack/react-router";
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
import { TanStackDevtools } from "@tanstack/react-devtools";
import { ClerkProvider } from "@clerk/clerk-react";
Expand Down
17 changes: 17 additions & 0 deletions clients/web/src/routes/no-org.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/no-org")({
component: NoOrgPage,
});

function NoOrgPage() {
return (
<div className="flex flex-col items-center justify-center min-h-screen px-8 text-center">
<h1 className="text-2xl font-semibold mb-4">No Organization Found</h1>
<p className="text-gray-500 max-w-md">
You're not part of a hotel organization yet. Please contact your manager
for an invitation.
</p>
</div>
);
}
Loading