2024-06-20 01:21:02 +02:00
import type {
Follow ,
FollowAccept ,
FollowReject ,
2024-08-26 19:06:49 +02:00
} from "@versia/federation/types" ;
2024-07-27 20:46:19 +02:00
import { type InferSelectModel , eq , sql } from "drizzle-orm" ;
2024-05-29 02:59:49 +02:00
import { db } from "~/drizzle/db" ;
2024-04-11 13:39:07 +02:00
import {
2024-04-17 08:36:01 +02:00
Applications ,
2024-06-30 10:24:10 +02:00
type Instances ,
2024-06-08 06:57:29 +02:00
type Roles ,
2024-04-17 08:36:01 +02:00
Tokens ,
2024-06-30 10:24:10 +02:00
type Users ,
2024-05-29 02:59:49 +02:00
} from "~/drizzle/schema" ;
2024-07-26 18:51:39 +02:00
import type { EmojiWithInstance } from "~/packages/database-interface/emoji" ;
2024-05-29 02:59:49 +02:00
import { User } from "~/packages/database-interface/user" ;
2024-10-04 15:22:48 +02:00
import type { Application } from "./application.ts" ;
import type { Token } from "./token.ts" ;
2024-04-11 13:39:07 +02:00
2024-04-25 05:40:27 +02:00
export type UserType = InferSelectModel < typeof Users > ;
2024-04-17 06:09:21 +02:00
2024-04-25 05:40:27 +02:00
export type UserWithInstance = UserType & {
2024-04-17 08:36:01 +02:00
instance : InferSelectModel < typeof Instances > | null ;
2024-04-11 13:39:07 +02:00
} ;
2024-03-11 03:04:14 +01:00
2024-04-25 05:40:27 +02:00
export type UserWithRelations = UserType & {
2024-04-17 08:36:01 +02:00
instance : InferSelectModel < typeof Instances > | null ;
2024-04-11 13:39:07 +02:00
emojis : EmojiWithInstance [ ] ;
followerCount : number ;
followingCount : number ;
statusCount : number ;
2024-06-08 06:57:29 +02:00
roles : InferSelectModel < typeof Roles > [ ] ;
2024-04-11 13:39:07 +02:00
} ;
2023-10-23 07:39:42 +02:00
2024-04-11 13:39:07 +02:00
export const userRelations : {
instance : true ;
emojis : {
with : {
emoji : {
with : {
instance : true ;
} ;
} ;
} ;
} ;
2024-06-08 06:57:29 +02:00
roles : {
with : {
role : true ;
} ;
} ;
2024-04-11 13:39:07 +02:00
} = {
instance : true ,
emojis : {
with : {
emoji : {
with : {
instance : true ,
} ,
} ,
} ,
} ,
2024-06-08 06:57:29 +02:00
roles : {
with : {
role : true ,
} ,
} ,
2024-04-11 13:39:07 +02:00
} ;
export const userExtras = {
followerCount :
2024-04-17 08:36:01 +02:00
sql ` (SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = "Users".id AND "relationships"."following" = true)) ` . as (
2024-04-11 13:39:07 +02:00
"follower_count" ,
) ,
followingCount :
2024-04-17 08:36:01 +02:00
sql ` (SELECT COUNT(*) FROM "Relationships" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "Users".id AND "relationshipSubjects"."following" = true)) ` . as (
2024-04-11 13:39:07 +02:00
"following_count" ,
) ,
statusCount :
2024-04-17 08:36:01 +02:00
sql ` (SELECT COUNT(*) FROM "Notes" WHERE "Notes"."authorId" = "Users".id) ` . as (
2024-04-11 13:39:07 +02:00
"status_count" ,
) ,
} ;
2023-11-04 04:34:31 +01:00
2024-04-11 13:39:07 +02:00
export const userExtrasTemplate = ( name : string ) = > ( {
2024-10-03 10:26:58 +02:00
// @ts-expect-error sql is a template tag, so it gets confused when we use it as a function
2024-04-11 13:39:07 +02:00
followerCount : sql ( [
2024-04-17 08:36:01 +02:00
` (SELECT COUNT(*) FROM "Relationships" "relationships" WHERE ("relationships"."ownerId" = " ${ name } ".id AND "relationships"."following" = true)) ` ,
2024-04-11 13:39:07 +02:00
] ) . as ( "follower_count" ) ,
2024-10-03 10:26:58 +02:00
// @ts-expect-error sql is a template tag, so it gets confused when we use it as a function
2024-04-11 13:39:07 +02:00
followingCount : sql ( [
2024-04-17 08:36:01 +02:00
` (SELECT COUNT(*) FROM "Relationships" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = " ${ name } ".id AND "relationshipSubjects"."following" = true)) ` ,
2024-04-11 13:39:07 +02:00
] ) . as ( "following_count" ) ,
2024-10-03 10:26:58 +02:00
// @ts-expect-error sql is a template tag, so it gets confused when we use it as a function
2024-04-11 13:39:07 +02:00
statusCount : sql ( [
2024-04-17 08:36:01 +02:00
` (SELECT COUNT(*) FROM "Notes" WHERE "Notes"."authorId" = " ${ name } ".id) ` ,
2024-04-11 13:39:07 +02:00
] ) . as ( "status_count" ) ,
2023-11-27 01:56:16 +01:00
} ) ;
2024-04-11 13:39:07 +02:00
export interface AuthData {
2024-04-25 05:40:27 +02:00
user : User | null ;
2024-04-11 13:39:07 +02:00
token : string ;
2024-04-16 04:09:16 +02:00
application : Application | null ;
2024-04-11 13:39:07 +02:00
}
2023-10-08 22:20:42 +02:00
2024-05-06 09:16:33 +02:00
export const getFromHeader = async ( value : string ) : Promise < AuthData > = > {
const token = value . split ( " " ) [ 1 ] ;
const { user , application } =
await retrieveUserAndApplicationFromToken ( token ) ;
return { user , token , application } ;
} ;
2024-04-10 10:07:03 +02:00
export const sendFollowAccept = async ( follower : User , followee : User ) = > {
2024-07-26 18:51:39 +02:00
await follower . federateToUser (
2024-08-19 15:16:01 +02:00
followAcceptToVersia ( follower , followee ) ,
2024-04-10 10:07:03 +02:00
followee ,
) ;
} ;
export const sendFollowReject = async ( follower : User , followee : User ) = > {
2024-07-26 18:51:39 +02:00
await follower . federateToUser (
2024-08-19 15:16:01 +02:00
followRejectToVersia ( follower , followee ) ,
2024-04-10 10:07:03 +02:00
followee ,
) ;
} ;
2024-04-11 13:39:07 +02:00
export const transformOutputToUserWithRelations = (
2024-04-25 05:40:27 +02:00
user : Omit < UserType , " endpoints " > & {
2024-04-11 13:39:07 +02:00
followerCount : unknown ;
followingCount : unknown ;
statusCount : unknown ;
emojis : {
2024-04-14 03:21:38 +02:00
userId : string ;
emojiId : string ;
2024-04-11 13:39:07 +02:00
emoji? : EmojiWithInstance ;
} [ ] ;
2024-04-17 08:36:01 +02:00
instance : InferSelectModel < typeof Instances > | null ;
2024-06-08 06:57:29 +02:00
roles : {
userId : string ;
roleId : string ;
role? : InferSelectModel < typeof Roles > ;
} [ ] ;
2024-04-11 13:39:07 +02:00
endpoints : unknown ;
} ,
) : UserWithRelations = > {
return {
. . . user ,
followerCount : Number ( user . followerCount ) ,
followingCount : Number ( user . followingCount ) ,
statusCount : Number ( user . statusCount ) ,
endpoints :
user . endpoints ? ?
( { } as Partial < {
dislikes : string ;
featured : string ;
likes : string ;
followers : string ;
following : string ;
inbox : string ;
outbox : string ;
} > ) ,
emojis : user.emojis.map (
( emoji ) = >
( emoji as unknown as Record < string , object > )
. emoji as EmojiWithInstance ,
) ,
2024-06-08 06:57:29 +02:00
roles : user.roles
. map ( ( role ) = > role . role )
. filter ( Boolean ) as InferSelectModel < typeof Roles > [ ] ,
2024-04-11 13:39:07 +02:00
} ;
} ;
export const findManyUsers = async (
2024-04-17 08:36:01 +02:00
query : Parameters < typeof db.query.Users.findMany > [ 0 ] ,
2024-04-11 13:39:07 +02:00
) : Promise < UserWithRelations [ ] > = > {
2024-04-17 08:36:01 +02:00
const output = await db . query . Users . findMany ( {
2024-04-11 13:39:07 +02:00
. . . query ,
with : {
. . . userRelations ,
. . . query ? . with ,
} ,
extras : {
. . . userExtras ,
. . . query ? . extras ,
} ,
} ) ;
return output . map ( ( user ) = > transformOutputToUserWithRelations ( user ) ) ;
} ;
2023-11-11 03:36:06 +01:00
/ * *
* Retrieves a user from a token .
* @param access_token The access token to retrieve the user from .
* @returns The user associated with the given access token .
* /
2024-04-11 13:39:07 +02:00
export const retrieveUserFromToken = async (
2024-06-13 04:26:43 +02:00
accessToken : string ,
2024-04-25 05:40:27 +02:00
) : Promise < User | null > = > {
2024-06-13 04:26:43 +02:00
if ( ! accessToken ) {
return null ;
}
2024-04-07 07:30:49 +02:00
2024-06-13 04:26:43 +02:00
const token = await retrieveToken ( accessToken ) ;
2024-04-07 07:30:49 +02:00
2024-06-13 04:26:43 +02:00
if ( ! token ? . userId ) {
return null ;
}
2024-04-11 13:39:07 +02:00
2024-04-25 05:40:27 +02:00
const user = await User . fromId ( token . userId ) ;
2024-04-07 07:30:49 +02:00
2024-04-11 13:39:07 +02:00
return user ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 05:18:05 +02:00
2024-04-16 04:09:16 +02:00
export const retrieveUserAndApplicationFromToken = async (
2024-06-13 04:26:43 +02:00
accessToken : string ,
2024-04-16 04:09:16 +02:00
) : Promise < {
2024-04-25 05:40:27 +02:00
user : User | null ;
2024-04-16 04:09:16 +02:00
application : Application | null ;
} > = > {
2024-06-13 04:26:43 +02:00
if ( ! accessToken ) {
return { user : null , application : null } ;
}
2024-04-16 04:09:16 +02:00
const output = (
await db
. select ( {
2024-04-17 08:36:01 +02:00
token : Tokens ,
application : Applications ,
2024-04-16 04:09:16 +02:00
} )
2024-04-17 08:36:01 +02:00
. from ( Tokens )
. leftJoin ( Applications , eq ( Tokens . applicationId , Applications . id ) )
2024-06-13 04:26:43 +02:00
. where ( eq ( Tokens . accessToken , accessToken ) )
2024-04-16 04:09:16 +02:00
. limit ( 1 )
) [ 0 ] ;
2024-06-13 04:26:43 +02:00
if ( ! output ? . token . userId ) {
return { user : null , application : null } ;
}
2024-04-16 04:09:16 +02:00
2024-04-25 05:40:27 +02:00
const user = await User . fromId ( output . token . userId ) ;
2024-04-16 04:09:16 +02:00
return { user , application : output.application ? ? null } ;
} ;
export const retrieveToken = async (
2024-06-13 04:26:43 +02:00
accessToken : string ,
2024-04-16 04:09:16 +02:00
) : Promise < Token | null > = > {
2024-06-13 04:26:43 +02:00
if ( ! accessToken ) {
return null ;
}
2024-04-16 04:09:16 +02:00
return (
2024-04-17 08:36:01 +02:00
( await db . query . Tokens . findFirst ( {
2024-06-13 04:26:43 +02:00
where : ( tokens , { eq } ) = > eq ( tokens . accessToken , accessToken ) ,
2024-04-16 04:09:16 +02:00
} ) ) ? ? null
) ;
} ;
2024-08-19 15:16:01 +02:00
export const followRequestToVersia = (
2024-04-10 07:51:00 +02:00
follower : User ,
followee : User ,
2024-06-20 01:21:02 +02:00
) : Follow = > {
2024-04-25 05:40:27 +02:00
if ( follower . isRemote ( ) ) {
2024-04-10 07:51:00 +02:00
throw new Error ( "Follower must be a local user" ) ;
}
2024-04-25 05:40:27 +02:00
if ( ! followee . isRemote ( ) ) {
2024-04-10 07:51:00 +02:00
throw new Error ( "Followee must be a remote user" ) ;
}
2024-06-13 02:45:07 +02:00
if ( ! followee . data . uri ) {
2024-04-10 07:51:00 +02:00
throw new Error ( "Followee must have a URI in database" ) ;
}
const id = crypto . randomUUID ( ) ;
return {
type : "Follow" ,
2024-10-03 13:41:58 +02:00
id ,
2024-04-25 05:40:27 +02:00
author : follower.getUri ( ) ,
followee : followee.getUri ( ) ,
2024-04-10 07:51:00 +02:00
created_at : new Date ( ) . toISOString ( ) ,
} ;
} ;
2024-08-19 15:16:01 +02:00
export const followAcceptToVersia = (
2024-04-10 07:51:00 +02:00
follower : User ,
followee : User ,
2024-06-20 01:21:02 +02:00
) : FollowAccept = > {
2024-04-25 05:40:27 +02:00
if ( ! follower . isRemote ( ) ) {
2024-04-10 09:18:41 +02:00
throw new Error ( "Follower must be a remote user" ) ;
2024-04-10 07:51:00 +02:00
}
2024-04-25 05:40:27 +02:00
if ( followee . isRemote ( ) ) {
2024-04-10 09:18:41 +02:00
throw new Error ( "Followee must be a local user" ) ;
2024-04-10 07:51:00 +02:00
}
2024-06-13 02:45:07 +02:00
if ( ! follower . data . uri ) {
2024-04-10 09:18:41 +02:00
throw new Error ( "Follower must have a URI in database" ) ;
2024-04-10 07:51:00 +02:00
}
const id = crypto . randomUUID ( ) ;
return {
type : "FollowAccept" ,
2024-10-03 13:41:58 +02:00
id ,
2024-04-25 05:40:27 +02:00
author : followee.getUri ( ) ,
2024-04-10 07:51:00 +02:00
created_at : new Date ( ) . toISOString ( ) ,
2024-04-25 05:40:27 +02:00
follower : follower.getUri ( ) ,
2024-04-10 07:51:00 +02:00
} ;
} ;
2024-04-10 10:07:03 +02:00
2024-08-19 15:16:01 +02:00
export const followRejectToVersia = (
2024-04-10 10:07:03 +02:00
follower : User ,
followee : User ,
2024-06-20 01:21:02 +02:00
) : FollowReject = > {
2024-04-10 10:07:03 +02:00
return {
2024-08-19 15:16:01 +02:00
. . . followAcceptToVersia ( follower , followee ) ,
2024-04-10 10:07:03 +02:00
type : "FollowReject" ,
} ;
} ;