gpl_core/instructions/
post.rs

1use crate::errors::{GumError, PostError};
2use crate::events::{PostCommentNew, PostDeleted, PostNew, PostUpdated};
3use crate::state::{Post, Profile, User, MAX_LEN_URI};
4use gpl_session::{session_auth_or, Session};
5
6use anchor_lang::prelude::*;
7use std::convert::AsRef;
8
9use crate::constants::*;
10
11use gpl_session::{SessionError, SessionToken};
12
13// Create Post
14#[derive(Accounts, Session)]
15#[instruction(metadata_uri: String, random_hash: [u8;32])]
16pub struct CreatePost<'info> {
17    // The account that will be initialized as a Post
18    #[account(
19        init,
20        seeds = [
21            POST_PREFIX_SEED.as_bytes(),
22            random_hash.as_ref(),
23        ],
24        bump,
25        payer = authority,
26        space = Post::LEN
27    )]
28    pub post: Account<'info, Post>,
29    #[account(
30        seeds = [
31            PROFILE_PREFIX_SEED.as_bytes(),
32            profile.namespace.as_ref().as_bytes(),
33            user.to_account_info().key.as_ref(),
34        ],
35        bump,
36        has_one = user,
37    )]
38    pub profile: Account<'info, Profile>,
39    #[account(
40        seeds = [
41            USER_PREFIX_SEED.as_bytes(),
42            user.random_hash.as_ref(),
43        ],
44        bump,
45    )]
46    pub user: Account<'info, User>,
47
48    #[session(
49        signer = authority,
50        authority = user.authority.key()
51    )]
52    pub session_token: Option<Account<'info, SessionToken>>,
53
54    #[account(mut)]
55    pub authority: Signer<'info>,
56    // The system program
57    pub system_program: Program<'info, System>,
58}
59
60// Handler to create a new Post account
61#[session_auth_or(
62    ctx.accounts.user.authority.key() == ctx.accounts.authority.key(),
63    GumError::UnauthorizedSigner
64)]
65pub fn create_post_handler(
66    ctx: Context<CreatePost>,
67    metadata_uri: String,
68    random_hash: [u8; 32],
69) -> Result<()> {
70    // CHECK metadata_uri length
71    require!(metadata_uri.len() <= MAX_LEN_URI, PostError::URITooLong);
72
73    let post = &mut ctx.accounts.post;
74    post.metadata_uri = metadata_uri;
75    post.random_hash = random_hash;
76    post.profile = *ctx.accounts.profile.to_account_info().key;
77    // emit new post event
78    emit!(PostNew {
79        post: *post.to_account_info().key,
80        profile: *ctx.accounts.profile.to_account_info().key,
81        user: *ctx.accounts.user.to_account_info().key,
82        random_hash: random_hash,
83        metadata_uri: post.metadata_uri.clone(),
84        timestamp: Clock::get()?.unix_timestamp,
85    });
86    Ok(())
87}
88
89// Update a post account
90#[derive(Accounts, Session)]
91#[instruction(metadata_uri: String)]
92pub struct UpdatePost<'info> {
93    // The Post account to update
94    #[account(
95        mut,
96        seeds = [
97            POST_PREFIX_SEED.as_bytes(),
98            post.random_hash.as_ref(),
99        ],
100        bump,
101        has_one = profile,
102    )]
103    pub post: Account<'info, Post>,
104    #[account(
105        seeds = [
106            PROFILE_PREFIX_SEED.as_bytes(),
107            profile.namespace.as_ref().as_bytes(),
108            user.to_account_info().key.as_ref(),
109        ],
110        bump,
111        has_one = user,
112    )]
113    pub profile: Account<'info, Profile>,
114    #[account(
115        seeds = [
116            USER_PREFIX_SEED.as_bytes(),
117            user.random_hash.as_ref(),
118        ],
119        bump,
120    )]
121    pub user: Account<'info, User>,
122    #[session(
123        signer = authority,
124        authority = user.authority.key()
125    )]
126    pub session_token: Option<Account<'info, SessionToken>>,
127    #[account(mut)]
128    pub authority: Signer<'info>,
129    pub system_program: Program<'info, System>,
130}
131
132// Handler to update a Post account
133#[session_auth_or(
134    ctx.accounts.user.authority.key() == ctx.accounts.authority.key(),
135    GumError::UnauthorizedSigner
136)]
137pub fn update_post_handler(ctx: Context<UpdatePost>, metadata_uri: String) -> Result<()> {
138    // CHECK metadata_uri length
139    require!(metadata_uri.len() <= MAX_LEN_URI, PostError::URITooLong);
140    let post = &mut ctx.accounts.post;
141    post.metadata_uri = metadata_uri;
142    // emit update post event
143    emit!(PostUpdated {
144        post: *post.to_account_info().key,
145        profile: *ctx.accounts.profile.to_account_info().key,
146        user: *ctx.accounts.user.to_account_info().key,
147        metadata_uri: post.metadata_uri.clone(),
148        timestamp: Clock::get()?.unix_timestamp,
149    });
150    Ok(())
151}
152
153// Create a comment as a new post account with reply_to set to the parent post
154#[derive(Accounts, Session)]
155#[instruction(metadata_uri: String, random_hash: [u8;32])]
156pub struct CreateComment<'info> {
157    // The account that will be initialized as a Post
158    #[account(
159        init,
160        seeds = [
161            POST_PREFIX_SEED.as_bytes(),
162            random_hash.as_ref(),
163        ],
164        bump,
165        payer = authority,
166        space = Post::LEN
167    )]
168    pub post: Account<'info, Post>,
169    #[account(
170        seeds = [
171            PROFILE_PREFIX_SEED.as_bytes(),
172            profile.namespace.as_ref().as_bytes(),
173            user.to_account_info().key.as_ref(),
174        ],
175        bump,
176        has_one = user,
177    )]
178    pub profile: Account<'info, Profile>,
179    #[account(
180        seeds = [
181            USER_PREFIX_SEED.as_bytes(),
182            user.random_hash.as_ref(),
183        ],
184        bump,
185    )]
186    pub user: Account<'info, User>,
187    #[account(
188        seeds = [
189            POST_PREFIX_SEED.as_bytes(),
190            reply_to.random_hash.as_ref(),
191        ],
192        bump,
193    )]
194    pub reply_to: Account<'info, Post>,
195    #[session(
196        signer = authority,
197        authority = user.authority.key()
198    )]
199    pub session_token: Option<Account<'info, SessionToken>>,
200    #[account(mut)]
201    pub authority: Signer<'info>,
202    // The system program
203    pub system_program: Program<'info, System>,
204}
205
206// Handler to add a comment to a post
207#[session_auth_or(
208    ctx.accounts.user.authority.key() == ctx.accounts.authority.key(),
209    GumError::UnauthorizedSigner
210)]
211pub fn create_comment_handler(
212    ctx: Context<CreateComment>,
213    metadata_uri: String,
214    random_hash: [u8; 32],
215) -> Result<()> {
216    // Check metadata_uri length
217    require!(metadata_uri.len() <= MAX_LEN_URI, PostError::URITooLong);
218
219    let post = &mut ctx.accounts.post;
220    post.metadata_uri = metadata_uri;
221    post.random_hash = random_hash;
222    post.profile = *ctx.accounts.profile.to_account_info().key;
223    post.reply_to = Some(*ctx.accounts.reply_to.to_account_info().key);
224    // emit new comment event
225    emit!(PostCommentNew {
226        post: *post.to_account_info().key,
227        profile: *ctx.accounts.profile.to_account_info().key,
228        user: *ctx.accounts.user.to_account_info().key,
229        random_hash: random_hash,
230        metadata_uri: post.metadata_uri.clone(),
231        reply_to: *ctx.accounts.reply_to.to_account_info().key,
232        timestamp: Clock::get()?.unix_timestamp,
233    });
234    Ok(())
235}
236
237// Delete a post account
238#[derive(Accounts, Session)]
239pub struct DeletePost<'info> {
240    // The Post account to delete
241    #[account(
242        mut,
243        seeds = [
244            POST_PREFIX_SEED.as_bytes(),
245            post.random_hash.as_ref(),
246        ],
247        bump,
248        has_one = profile,
249        close = refund_receiver,
250    )]
251    pub post: Account<'info, Post>,
252    #[account(
253        seeds = [
254            PROFILE_PREFIX_SEED.as_bytes(),
255            profile.namespace.as_ref().as_bytes(),
256            user.to_account_info().key.as_ref(),
257        ],
258        bump,
259        has_one = user,
260    )]
261    pub profile: Account<'info, Profile>,
262    #[account(
263        seeds = [
264            USER_PREFIX_SEED.as_bytes(),
265            user.random_hash.as_ref(),
266        ],
267        bump,
268    )]
269    pub user: Account<'info, User>,
270
271    #[session(
272        signer = authority,
273        authority = user.authority.key()
274    )]
275    pub session_token: Option<Account<'info, SessionToken>>,
276    #[account(mut)]
277    pub authority: Signer<'info>,
278    #[account(mut, constraint = refund_receiver.key() == user.authority)]
279    pub refund_receiver: SystemAccount<'info>,
280    pub system_program: Program<'info, System>,
281}
282
283// Handler to delete a Post account
284#[session_auth_or(
285    ctx.accounts.user.authority.key() == ctx.accounts.authority.key(),
286    GumError::UnauthorizedSigner
287)]
288pub fn delete_post_handler(ctx: Context<DeletePost>) -> Result<()> {
289    // emit delete post event
290    emit!(PostDeleted {
291        post: *ctx.accounts.post.to_account_info().key,
292        profile: *ctx.accounts.profile.to_account_info().key,
293        user: *ctx.accounts.user.to_account_info().key,
294        timestamp: Clock::get()?.unix_timestamp,
295    });
296    Ok(())
297}