11import { randomUUID } from 'node:crypto' ;
2- import { BaseEntity } from '../core/base.entity' ;
32import { type CreatePostType , PostType } from '../post/post.entity' ;
4- import type { Site } from '../site/site.service' ;
53
6- export type PersistedAccount = Account & { id : number } ;
4+ export interface Account {
5+ readonly id : number ;
6+ readonly uuid : string ;
7+ readonly username : string ;
8+ readonly name : string | null ;
9+ readonly bio : string | null ;
10+ readonly url : URL ;
11+ readonly avatarUrl : URL | null ;
12+ readonly bannerImageUrl : URL | null ;
13+ readonly apId : URL ;
14+ readonly apFollowers : URL | null ;
15+ readonly isInternal : boolean ;
16+ /**
17+ * Returns a new Account instance which needs to be saved.
18+ */
19+ updateProfile ( params : ProfileUpdateParams ) : Account ;
20+ /**
21+ * @deprecated
22+ */
23+ getApIdForPost ( post : { type : CreatePostType ; uuid : string } ) : URL ;
24+ }
725
8- export interface AccountData {
9- id : number ;
10- uuid : string | null ;
26+ export interface AccountDraft {
27+ uuid : string ;
1128 username : string ;
1229 name : string | null ;
1330 bio : string | null ;
31+ url : URL ;
1432 avatarUrl : URL | null ;
1533 bannerImageUrl : URL | null ;
16- site : Site | null ;
17- apId : URL | null ;
18- url : URL | null ;
34+ apId : URL ;
1935 apFollowers : URL | null ;
36+ isInternal : boolean ;
2037}
2138
22- export type AccountSite = {
23- id : number ;
24- host : string ;
25- } ;
26-
27- export interface ProfileUpdateParams {
28- name ?: string | null ;
29- bio ?: string | null ;
30- username ?: string ;
31- avatarUrl ?: URL | null ;
32- bannerImageUrl ?: URL | null ;
33- }
34-
35- export class Account extends BaseEntity {
36- public readonly uuid : string ;
37- public readonly url : URL ;
38- public readonly apId : URL ;
39- public readonly apFollowers : URL ;
40-
41- private _name : string | null ;
42- private _bio : string | null ;
43- private _username : string ;
44- private _avatarUrl : URL | null ;
45- private _bannerImageUrl : URL | null ;
46-
39+ export class AccountEntity implements Account {
4740 constructor (
48- public readonly id : number | null ,
49- uuid : string | null ,
50- username : string ,
51- name : string | null ,
52- bio : string | null ,
53- avatarUrl : URL | null ,
54- bannerImageUrl : URL | null ,
55- private readonly site : AccountSite | null ,
56- apId : URL | null ,
57- url : URL | null ,
58- apFollowers : URL | null ,
59- ) {
60- super ( id ) ;
61-
62- this . _name = name ;
63- this . _bio = bio ;
64- this . _username = username ;
65- this . _avatarUrl = avatarUrl ;
66- this . _bannerImageUrl = bannerImageUrl ;
67-
68- if ( uuid === null ) {
69- this . uuid = randomUUID ( ) ;
70- } else {
71- this . uuid = uuid ;
72- }
73- if ( apId === null ) {
74- this . apId = this . getApId ( ) ;
75- } else {
76- this . apId = apId ;
77- }
78- if ( apFollowers === null ) {
79- this . apFollowers = this . getApFollowers ( ) ;
80- } else {
81- this . apFollowers = apFollowers ;
82- }
83- if ( url === null ) {
84- this . url = this . apId ;
85- } else {
86- this . url = url ;
87- }
88- }
89-
90- get name ( ) : string | null {
91- return this . _name ;
92- }
93-
94- get bio ( ) : string | null {
95- return this . _bio ;
96- }
97-
98- get username ( ) : string {
99- return this . _username ;
100- }
101-
102- get avatarUrl ( ) : URL | null {
103- return this . _avatarUrl ;
104- }
105-
106- get bannerImageUrl ( ) : URL | null {
107- return this . _bannerImageUrl ;
108- }
109-
110- updateProfile ( params : ProfileUpdateParams ) : void {
111- if ( params . name !== undefined ) {
112- this . _name = params . name ;
113- }
114-
115- if ( params . bio !== undefined ) {
116- this . _bio = params . bio ;
117- }
118-
119- if ( params . username !== undefined ) {
120- this . _username = params . username ;
121- }
122-
123- if ( params . avatarUrl !== undefined ) {
124- this . _avatarUrl = params . avatarUrl ;
125- }
126-
127- if ( params . bannerImageUrl !== undefined ) {
128- this . _bannerImageUrl = params . bannerImageUrl ;
129- }
130- }
131-
132- get isInternal ( ) {
133- return this . site !== null ;
134- }
135-
136- getApId ( ) {
137- if ( ! this . isInternal ) {
138- throw new Error ( 'Cannot get AP ID for External Accounts' ) ;
139- }
140-
141- return new URL (
142- '.ghost/activitypub/users/index' ,
143- `${ Account . protocol } ://${ this . site ! . host } ` ,
41+ public readonly id : number ,
42+ public readonly uuid : string ,
43+ public readonly username : string ,
44+ public readonly name : string | null ,
45+ public readonly bio : string | null ,
46+ public readonly url : URL ,
47+ public readonly avatarUrl : URL | null ,
48+ public readonly bannerImageUrl : URL | null ,
49+ public readonly apId : URL ,
50+ public readonly apFollowers : URL | null ,
51+ public readonly isInternal : boolean ,
52+ ) { }
53+
54+ static create ( data : Data < Account > ) {
55+ return new AccountEntity (
56+ data . id ,
57+ data . uuid ,
58+ data . username ,
59+ data . name ,
60+ data . bio ,
61+ data . url ,
62+ data . avatarUrl ,
63+ data . bannerImageUrl ,
64+ data . apId ,
65+ data . apFollowers ,
66+ data . isInternal ,
14467 ) ;
14568 }
14669
147- getApFollowers ( ) {
148- if ( ! this . isInternal ) {
149- throw new Error ( 'Cannot get AP Followers for External Accounts' ) ;
150- }
151-
152- return new URL (
153- '.ghost/activitypub/followers/index' ,
154- `${ Account . protocol } ://${ this . site ! . host } ` ,
155- ) ;
70+ static draft ( from : AccountDraftData ) : AccountDraft {
71+ const uuid = randomUUID ( ) ;
72+ const apId = ! from . isInternal
73+ ? from . apId
74+ : new URL ( '.ghost/activitypub/users/index' , from . host ) ;
75+ const apFollowers = ! from . isInternal
76+ ? from . apFollowers
77+ : new URL ( '.ghost/activitypub/followers/index' , from . host ) ;
78+ const url = from . url || apId ;
79+ return {
80+ ...from ,
81+ uuid,
82+ url,
83+ apId,
84+ apFollowers,
85+ } ;
15686 }
15787
158- getApIdForPost ( post : { type : CreatePostType ; uuid : string } ) {
88+ getApIdForPost ( post : { type : CreatePostType ; uuid : string } ) : URL {
15989 if ( ! this . isInternal ) {
16090 throw new Error ( 'Cannot get AP ID for External Accounts' ) ;
16191 }
@@ -174,28 +104,65 @@ export class Account extends BaseEntity {
174104 }
175105 }
176106
177- return new URL (
178- `.ghost/activitypub/${ type } /${ post . uuid } ` ,
179- `${ Account . protocol } ://${ this . site ! . host } ` ,
180- ) ;
107+ return new URL ( `.ghost/activitypub/${ type } /${ post . uuid } ` , this . apId ) ;
181108 }
182109
183- private static protocol : 'http' | 'https' =
184- process . env . NODE_ENV === 'testing' ? 'http' : 'https' ;
185-
186- static createFromData ( data : AccountData ) {
187- return new Account (
188- data . id ,
189- data . uuid ,
190- data . username ,
191- data . name ,
192- data . bio ,
193- data . avatarUrl ,
194- data . bannerImageUrl ,
195- data . site ,
196- data . apId ,
197- data . url ,
198- data . apFollowers ,
199- ) ;
110+ updateProfile ( params : ProfileUpdateParams ) {
111+ type P = ProfileUpdateParams ;
112+ const get = < K extends keyof P > ( prop : K ) : P [ K ] =>
113+ params [ prop ] === undefined ? this [ prop ] : params [ prop ] ;
114+
115+ return AccountEntity . create ( {
116+ ...this ,
117+ username : get ( 'username' ) ,
118+ name : get ( 'name' ) ,
119+ bio : get ( 'bio' ) ,
120+ avatarUrl : get ( 'avatarUrl' ) ,
121+ bannerImageUrl : get ( 'bannerImageUrl' ) ,
122+ } ) ;
200123 }
201124}
125+
126+ type ProfileUpdateParams = {
127+ name ?: string | null ;
128+ bio ?: string | null ;
129+ username ?: string ;
130+ avatarUrl ?: URL | null ;
131+ bannerImageUrl ?: URL | null ;
132+ } ;
133+
134+ /**
135+ * Internal accounts require a `host` so we can calculate the ActivityPub URLs
136+ */
137+ type InternalAccountDraftData = {
138+ isInternal : true ;
139+ host : URL ;
140+ username : string ;
141+ name : string ;
142+ bio : string | null ;
143+ url : URL | null ;
144+ avatarUrl : URL | null ;
145+ bannerImageUrl : URL | null ;
146+ } ;
147+
148+ /**
149+ * External accounts require the ActivityPub URLs to be passed in
150+ */
151+ type ExternalAccountDraftData = {
152+ isInternal : false ;
153+ username : string ;
154+ name : string ;
155+ bio : string | null ;
156+ url : URL | null ;
157+ avatarUrl : URL | null ;
158+ bannerImageUrl : URL | null ;
159+ apId : URL ;
160+ apFollowers : URL | null ;
161+ } ;
162+
163+ type AccountDraftData = InternalAccountDraftData | ExternalAccountDraftData ;
164+
165+ type Data < T > = {
166+ // biome-ignore lint/suspicious/noExplicitAny: These are not publicly usable instances of any
167+ [ K in keyof T as T [ K ] extends ( ...args : any [ ] ) => any ? never : K ] : T [ K ] ;
168+ } ;
0 commit comments