-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2b88aef
commit 4fde81d
Showing
15 changed files
with
358 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,17 @@ | ||
import { useCallback } from 'react'; | ||
|
||
import { trpc } from '../utils/trpcClient'; | ||
|
||
interface CrewUser { | ||
email: string; | ||
name?: string; | ||
login?: string; | ||
} | ||
import { CrewUser } from '../utils/db/types'; | ||
|
||
export const useUserResource = () => { | ||
const createUserMutation = trpc.user.сreateUserByCrew.useMutation(); | ||
const getUsersByCrewMutation = trpc.user.getLocalUsersByCrew.useMutation(); | ||
|
||
const createUserByCrew = useCallback( | ||
async (user: CrewUser) => createUserMutation.mutateAsync(user), | ||
[createUserMutation], | ||
const getUsersByCrew = useCallback( | ||
async (users: CrewUser[]) => getUsersByCrewMutation.mutateAsync(users), | ||
[getUsersByCrewMutation], | ||
); | ||
|
||
return { | ||
createUserByCrew, | ||
getUsersByCrew, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,18 @@ | ||
import { getUrlsFromString } from './url'; | ||
|
||
const crewLinkRegExp = new RegExp(`^${process.env.NEXT_PUBLIC_CREW_URL}(ru/|en/)?(?<login>[^/]+)/?$`); | ||
|
||
export const parseCrewLink = (link: string): string => { | ||
return link.match(crewLinkRegExp)?.groups?.login ?? ''; | ||
}; | ||
|
||
export const parseCrewLoginFromText = (text: string) => | ||
getUrlsFromString(text).reduce<string[]>((acum, url) => { | ||
const login = parseCrewLink(url); | ||
|
||
if (login) { | ||
acum.push(login); | ||
} | ||
|
||
return acum; | ||
}, []); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import { Activity, User } from '@prisma/client'; | ||
import { TRPCError } from '@trpc/server'; | ||
|
||
import { prisma } from '../prisma'; | ||
|
||
import { CrewUser } from './types'; | ||
|
||
export const getToken = () => { | ||
const authorization = process.env.CREW_API_TOKEN; | ||
|
||
if (!authorization) { | ||
throw new TRPCError({ code: 'FORBIDDEN', message: 'No api token for crew' }); | ||
} | ||
|
||
return authorization; | ||
}; | ||
|
||
type UserActivity = Activity & { user?: User | null }; | ||
|
||
export const getLocalUsersByCrew = async (crewUsers: CrewUser[]) => { | ||
const logins = crewUsers.reduce<string[]>((acum, { login }) => { | ||
if (login) { | ||
acum.push(login); | ||
} | ||
|
||
return acum; | ||
}, []); | ||
|
||
const emails = crewUsers.map(({ email }) => email); | ||
|
||
const existedActivities = await prisma.user.findMany({ | ||
where: { | ||
OR: [ | ||
{ | ||
nickname: { | ||
in: logins, | ||
}, | ||
}, | ||
{ | ||
email: { | ||
in: emails, | ||
}, | ||
}, | ||
], | ||
}, | ||
include: { | ||
activity: { | ||
include: { | ||
user: true, | ||
ghost: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const activityMap = existedActivities.reduce<{ | ||
byEmail: Record<string, UserActivity>; | ||
byLogin: Record<string, UserActivity>; | ||
}>( | ||
(acum, { nickname, email, activity }) => { | ||
if (activity) { | ||
if (nickname) { | ||
acum.byLogin[nickname] = activity; | ||
} | ||
acum.byEmail[email] = activity; | ||
} | ||
|
||
return acum; | ||
}, | ||
{ | ||
byEmail: {}, | ||
byLogin: {}, | ||
}, | ||
); | ||
|
||
const newCrewUsers = crewUsers.filter(({ login, email }) => { | ||
const hasLogin = login && activityMap.byLogin[login]; | ||
const hasEmail = activityMap.byEmail[email]; | ||
|
||
return !hasLogin && !hasEmail; | ||
}); | ||
|
||
const newActivities = await prisma.$transaction( | ||
newCrewUsers.map((item) => | ||
prisma.user.create({ | ||
data: { | ||
email: item.email, | ||
name: item.name, | ||
nickname: item.login, | ||
activity: { | ||
create: { | ||
settings: { | ||
create: {}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
include: { | ||
activity: { | ||
include: { user: true, ghost: true }, | ||
}, | ||
}, | ||
}), | ||
), | ||
); | ||
|
||
newActivities.forEach(({ activity, email }) => { | ||
if (activity) { | ||
activityMap.byEmail[email] = activity; | ||
} | ||
}); | ||
|
||
return crewUsers.reduce<{ | ||
items: UserActivity[]; | ||
activityByCrewId: Record<string, UserActivity>; | ||
}>( | ||
(acum, item) => { | ||
const activity = (item.login && activityMap.byLogin[item.login]) || activityMap.byEmail[item.email]; | ||
|
||
acum.items.push(activity); | ||
acum.activityByCrewId[item.id] = activity; | ||
|
||
return acum; | ||
}, | ||
{ | ||
items: [], | ||
activityByCrewId: {}, | ||
}, | ||
); | ||
}; | ||
|
||
export const getCrewUserByLogin = async (login: string) => { | ||
if (!process.env.NEXT_PUBLIC_CREW_URL) { | ||
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'No crew integration url provided' }); | ||
} | ||
|
||
const response = await fetch( | ||
`${process.env.NEXT_PUBLIC_CREW_URL}/api/rest/users/get-by-field?${new URLSearchParams({ | ||
login, | ||
})}`, | ||
{ | ||
method: 'GET', | ||
headers: { | ||
authorization: getToken(), | ||
'Content-Type': 'application/json', | ||
}, | ||
}, | ||
); | ||
|
||
if (!response.ok) { | ||
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: response.statusText }); | ||
} | ||
|
||
const data: CrewUser = await response.json(); | ||
|
||
return data; | ||
}; | ||
|
||
export const getCrewUsersByLogin = async (logins: string[]) => { | ||
if (!process.env.NEXT_PUBLIC_CREW_URL) { | ||
return []; | ||
} | ||
|
||
return Promise.all(logins.map((login) => getCrewUserByLogin(login))); | ||
}; | ||
|
||
export const getLocalUsersByCrewLogin = async (logins: string[]) => { | ||
const localUsers = await prisma.user.findMany({ | ||
where: { | ||
nickname: { | ||
in: logins, | ||
}, | ||
}, | ||
include: { | ||
activity: { | ||
include: { | ||
user: true, | ||
ghost: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const userByLogin = localUsers.reduce<Record<string, UserActivity>>((acum, u) => { | ||
if (u.activity && u.nickname) { | ||
acum[u.nickname] = u.activity; | ||
} | ||
return acum; | ||
}, {}); | ||
|
||
const newCrewUsers = await getCrewUsersByLogin(logins.filter((login) => !userByLogin[login])); | ||
const { activityByCrewId } = await getLocalUsersByCrew(newCrewUsers); | ||
|
||
newCrewUsers.forEach(({ login, id }) => { | ||
const localUser = activityByCrewId[id]; | ||
|
||
if (login && localUser) { | ||
userByLogin[login] = localUser; | ||
} | ||
}); | ||
|
||
return { | ||
items: logins.map((login) => userByLogin[login]), | ||
userByLogin, | ||
}; | ||
}; |
File renamed without changes.
Oops, something went wrong.