2020-11-18 09:16:51 +00:00
package communities
import (
2021-01-11 10:32:51 +00:00
"context"
2020-11-18 09:16:51 +00:00
"crypto/ecdsa"
"database/sql"
2021-01-11 10:32:51 +00:00
"errors"
2020-11-18 09:16:51 +00:00
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
2021-01-11 10:32:51 +00:00
"github.com/status-im/status-go/protocol/common"
2020-11-18 09:16:51 +00:00
"github.com/status-im/status-go/protocol/protobuf"
)
type Persistence struct {
db * sql . DB
logger * zap . Logger
}
2021-01-11 10:32:51 +00:00
const communitiesBaseQuery = ` SELECT c.id, c.private_key, c.description,c.joined,c.verified,r.clock FROM communities_communities c LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ? `
2020-11-18 09:16:51 +00:00
func ( p * Persistence ) SaveCommunity ( community * Community ) error {
id := community . ID ( )
privateKey := community . PrivateKey ( )
description , err := community . ToBytes ( )
if err != nil {
return err
}
_ , err = p . db . Exec ( ` INSERT INTO communities_communities (id, private_key, description, joined, verified) VALUES (?, ?, ?,?,?) ` , id , crypto . FromECDSA ( privateKey ) , description , community . config . Joined , community . config . Verified )
return err
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) queryCommunities ( memberIdentity * ecdsa . PublicKey , query string ) ( response [ ] * Community , err error ) {
2020-11-18 09:16:51 +00:00
2021-01-11 10:32:51 +00:00
rows , err := p . db . Query ( query , common . PubkeyToHex ( memberIdentity ) )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
2020-12-22 11:00:13 +00:00
defer func ( ) {
if err != nil {
// Don't shadow original error
_ = rows . Close ( )
return
}
err = rows . Close ( )
} ( )
2020-11-18 09:16:51 +00:00
for rows . Next ( ) {
var publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte
var joined bool
var verified bool
2021-01-11 10:32:51 +00:00
var requestedToJoinAt sql . NullInt64
err := rows . Scan ( & publicKeyBytes , & privateKeyBytes , & descriptionBytes , & joined , & verified , & requestedToJoinAt )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
2021-01-11 10:32:51 +00:00
org , err := unmarshalCommunityFromDB ( memberIdentity , publicKeyBytes , privateKeyBytes , descriptionBytes , joined , verified , uint64 ( requestedToJoinAt . Int64 ) , p . logger )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
response = append ( response , org )
}
return response , nil
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) AllCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
return p . queryCommunities ( memberIdentity , communitiesBaseQuery )
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) JoinedCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
query := communitiesBaseQuery + ` WHERE c.joined `
return p . queryCommunities ( memberIdentity , query )
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) CreatedCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
query := communitiesBaseQuery + ` WHERE c.private_key IS NOT NULL `
return p . queryCommunities ( memberIdentity , query )
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) GetByID ( memberIdentity * ecdsa . PublicKey , id [ ] byte ) ( * Community , error ) {
2020-11-18 09:16:51 +00:00
var publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte
var joined bool
var verified bool
2021-01-11 10:32:51 +00:00
var requestedToJoinAt sql . NullInt64
2020-11-18 09:16:51 +00:00
2021-01-11 10:32:51 +00:00
err := p . db . QueryRow ( communitiesBaseQuery + ` WHERE c.id = ? ` , common . PubkeyToHex ( memberIdentity ) , id ) . Scan ( & publicKeyBytes , & privateKeyBytes , & descriptionBytes , & joined , & verified , & requestedToJoinAt )
2020-11-18 09:16:51 +00:00
if err == sql . ErrNoRows {
return nil , nil
} else if err != nil {
return nil , err
}
2021-01-11 10:32:51 +00:00
return unmarshalCommunityFromDB ( memberIdentity , publicKeyBytes , privateKeyBytes , descriptionBytes , joined , verified , uint64 ( requestedToJoinAt . Int64 ) , p . logger )
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
func unmarshalCommunityFromDB ( memberIdentity * ecdsa . PublicKey , publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte , joined , verified bool , requestedToJoinAt uint64 , logger * zap . Logger ) ( * Community , error ) {
2020-11-18 09:16:51 +00:00
var privateKey * ecdsa . PrivateKey
var err error
if privateKeyBytes != nil {
privateKey , err = crypto . ToECDSA ( privateKeyBytes )
if err != nil {
return nil , err
}
}
metadata := & protobuf . ApplicationMetadataMessage { }
err = proto . Unmarshal ( descriptionBytes , metadata )
if err != nil {
return nil , err
}
description := & protobuf . CommunityDescription { }
err = proto . Unmarshal ( metadata . Payload , description )
if err != nil {
return nil , err
}
id , err := crypto . DecompressPubkey ( publicKeyBytes )
if err != nil {
return nil , err
}
config := Config {
PrivateKey : privateKey ,
CommunityDescription : description ,
2021-01-11 10:32:51 +00:00
MemberIdentity : memberIdentity ,
2020-11-18 09:16:51 +00:00
MarshaledCommunityDescription : descriptionBytes ,
Logger : logger ,
ID : id ,
Verified : verified ,
2021-01-11 10:32:51 +00:00
RequestedToJoinAt : requestedToJoinAt ,
2020-11-18 09:16:51 +00:00
Joined : joined ,
}
return New ( config )
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) SaveRequestToJoin ( request * RequestToJoin ) ( err error ) {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
var clock uint64
// Fetch any existing request to join
err = tx . QueryRow ( ` SELECT clock FROM communities_requests_to_join WHERE state = ? AND public_key = ? AND community_id = ? ` , RequestToJoinStatePending , request . PublicKey , request . CommunityID ) . Scan ( & clock )
if err != nil && err != sql . ErrNoRows {
return err
}
// This is already processed
if clock >= request . Clock {
return errors . New ( "old request to join" )
}
_ , err = tx . Exec ( ` INSERT INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) VALUES (?, ?, ?, ?, ?, ?, ?) ` , request . ID , request . PublicKey , request . Clock , request . ENSName , request . ChatID , request . CommunityID , request . State )
return err
}
func ( p * Persistence ) PendingRequestsToJoinForUser ( pk string ) ( [ ] * RequestToJoin , error ) {
var requests [ ] * RequestToJoin
rows , err := p . db . Query ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND public_key = ? ` , RequestToJoinStatePending , pk )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
request := & RequestToJoin { }
err := rows . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
requests = append ( requests , request )
}
return requests , nil
}
func ( p * Persistence ) HasPendingRequestsToJoinForUserAndCommunity ( userPk string , communityID [ ] byte ) ( bool , error ) {
var count int
err := p . db . QueryRow ( ` SELECT count(1) FROM communities_requests_to_join WHERE state = ? AND public_key = ? AND community_id = ? ` , RequestToJoinStatePending , userPk , communityID ) . Scan ( & count )
if err != nil {
return false , err
}
return count > 0 , nil
}
func ( p * Persistence ) PendingRequestsToJoinForCommunity ( id [ ] byte ) ( [ ] * RequestToJoin , error ) {
var requests [ ] * RequestToJoin
rows , err := p . db . Query ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND community_id = ? ` , RequestToJoinStatePending , id )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
request := & RequestToJoin { }
err := rows . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
requests = append ( requests , request )
}
return requests , nil
}
func ( p * Persistence ) SetRequestToJoinState ( pk string , communityID [ ] byte , state uint ) error {
_ , err := p . db . Exec ( ` UPDATE communities_requests_to_join SET state = ? WHERE community_id = ? AND public_key = ? ` , state , communityID , pk )
return err
}
func ( p * Persistence ) GetRequestToJoin ( id [ ] byte ) ( * RequestToJoin , error ) {
request := & RequestToJoin { }
err := p . db . QueryRow ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE id = ? ` , id ) . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
return request , nil
}