collection.go 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. package repo
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. t "time"
  9. "github.com/prometheus/client_golang/prometheus"
  10. "github.com/sirupsen/logrus"
  11. "github.com/ente-io/stacktrace"
  12. "github.com/ente-io/museum/ente"
  13. "github.com/ente-io/museum/pkg/utils/crypto"
  14. "github.com/ente-io/museum/pkg/utils/time"
  15. "github.com/lib/pq"
  16. )
  17. // CollectionRepository defines the methods for inserting, updating and
  18. // retrieving collection entities from the underlying repository
  19. type CollectionRepository struct {
  20. DB *sql.DB
  21. FileRepo *FileRepository
  22. PublicCollectionRepo *PublicCollectionRepository
  23. TrashRepo *TrashRepository
  24. SecretEncryptionKey []byte
  25. QueueRepo *QueueRepository
  26. LatencyLogger *prometheus.HistogramVec
  27. }
  28. type SharedCollection struct {
  29. CollectionID int64
  30. ToUserID int64
  31. FromUserID int64
  32. }
  33. // Create creates a collection
  34. func (repo *CollectionRepository) Create(c ente.Collection) (ente.Collection, error) {
  35. // Check if the app type can create collection
  36. if !ente.App(c.App).IsValidForCollection() {
  37. return ente.Collection{}, ente.ErrInvalidApp
  38. }
  39. err := repo.DB.QueryRow(`INSERT INTO collections(owner_id, encrypted_key, key_decryption_nonce, name, encrypted_name, name_decryption_nonce, type, attributes, updation_time, magic_metadata, pub_magic_metadata, app)
  40. VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING collection_id`,
  41. c.Owner.ID, c.EncryptedKey, c.KeyDecryptionNonce, c.Name, c.EncryptedName, c.NameDecryptionNonce, c.Type, c.Attributes, c.UpdationTime, c.MagicMetadata, c.PublicMagicMetadata, c.App).Scan(&c.ID)
  42. if err != nil {
  43. if err.Error() == "pq: duplicate key value violates unique constraint \"collections_favorites_constraint_index\"" {
  44. return ente.Collection{}, ente.ErrFavoriteCollectionAlreadyExist
  45. } else if err.Error() == "pq: duplicate key value violates unique constraint \"collections_uncategorized_constraint_index_v2\"" {
  46. return ente.Collection{}, ente.ErrUncategorizeCollectionAlreadyExists
  47. }
  48. }
  49. return c, stacktrace.Propagate(err, "")
  50. }
  51. // Get returns a collection identified by the collectionID
  52. func (repo *CollectionRepository) Get(collectionID int64) (ente.Collection, error) {
  53. row := repo.DB.QueryRow(`SELECT collection_id, owner_id, encrypted_key, key_decryption_nonce, name, encrypted_name, name_decryption_nonce, type, attributes, updation_time, is_deleted, magic_metadata, pub_magic_metadata
  54. FROM collections
  55. WHERE collection_id = $1`, collectionID)
  56. var c ente.Collection
  57. var name, encryptedName, nameDecryptionNonce sql.NullString
  58. if err := row.Scan(&c.ID, &c.Owner.ID, &c.EncryptedKey, &c.KeyDecryptionNonce, &name, &encryptedName, &nameDecryptionNonce, &c.Type, &c.Attributes, &c.UpdationTime, &c.IsDeleted, &c.MagicMetadata, &c.PublicMagicMetadata); err != nil {
  59. return c, stacktrace.Propagate(err, "")
  60. }
  61. if name.Valid && len(name.String) > 0 {
  62. c.Name = name.String
  63. } else {
  64. c.EncryptedName = encryptedName.String
  65. c.NameDecryptionNonce = nameDecryptionNonce.String
  66. }
  67. urlMap, err := repo.PublicCollectionRepo.GetCollectionToActivePublicURLMap(context.Background(), []int64{collectionID})
  68. if err != nil {
  69. return ente.Collection{}, stacktrace.Propagate(err, "failed to get publicURL info")
  70. }
  71. if publicUrls, ok := urlMap[collectionID]; ok {
  72. c.PublicURLs = publicUrls
  73. }
  74. return c, nil
  75. }
  76. func (repo *CollectionRepository) GetCollectionByType(userID int64, collectionType string) (ente.Collection, error) {
  77. row := repo.DB.QueryRow(`SELECT collection_id, owner_id, encrypted_key, key_decryption_nonce, name, encrypted_name, name_decryption_nonce, type, attributes, updation_time, is_deleted, magic_metadata
  78. FROM collections
  79. WHERE owner_id = $1 and type = $2`, userID, collectionType)
  80. var c ente.Collection
  81. var name, encryptedName, nameDecryptionNonce sql.NullString
  82. if err := row.Scan(&c.ID, &c.Owner.ID, &c.EncryptedKey, &c.KeyDecryptionNonce, &name, &encryptedName, &nameDecryptionNonce, &c.Type, &c.Attributes, &c.UpdationTime, &c.IsDeleted, &c.MagicMetadata); err != nil {
  83. return c, stacktrace.Propagate(err, "")
  84. }
  85. if name.Valid && len(name.String) > 0 {
  86. c.Name = name.String
  87. } else {
  88. c.EncryptedName = encryptedName.String
  89. c.NameDecryptionNonce = nameDecryptionNonce.String
  90. }
  91. return c, nil
  92. }
  93. // GetCollectionsOwnedByUser returns the list of collections that a user owns
  94. // todo: refactor this method
  95. func (repo *CollectionRepository) GetCollectionsOwnedByUser(userID int64, updationTime int64, app ente.App) ([]ente.Collection, error) {
  96. rows, err := repo.DB.Query(`
  97. SELECT collections.collection_id, collections.owner_id, collections.encrypted_key, collections.key_decryption_nonce, collections.name, collections.encrypted_name, collections.name_decryption_nonce, collections.type, collections.app, collections.attributes, collections.updation_time, collections.is_deleted, collections.magic_metadata, collections.pub_magic_metadata
  98. FROM collections
  99. WHERE collections.owner_id = $1 AND collections.updation_time > $2 AND app = $3`, userID, updationTime, strings.ToLower(string(app)))
  100. if err != nil {
  101. return nil, stacktrace.Propagate(err, "")
  102. }
  103. defer rows.Close()
  104. collectionIDs := make([]int64, 0)
  105. collections := make([]ente.Collection, 0)
  106. result := make([]ente.Collection, 0)
  107. for rows.Next() {
  108. var c ente.Collection
  109. var name, encryptedName, nameDecryptionNonce sql.NullString
  110. if err := rows.Scan(&c.ID, &c.Owner.ID, &c.EncryptedKey, &c.KeyDecryptionNonce, &name, &encryptedName, &nameDecryptionNonce, &c.Type, &c.App, &c.Attributes, &c.UpdationTime, &c.IsDeleted, &c.MagicMetadata, &c.PublicMagicMetadata); err != nil {
  111. return collections, stacktrace.Propagate(err, "")
  112. }
  113. if name.Valid && len(name.String) > 0 {
  114. c.Name = name.String
  115. } else {
  116. c.EncryptedName = encryptedName.String
  117. c.NameDecryptionNonce = nameDecryptionNonce.String
  118. }
  119. // TODO: Pull this information in the previous query
  120. sharees, err := repo.GetSharees(c.ID)
  121. if err != nil {
  122. return collections, stacktrace.Propagate(err, "")
  123. }
  124. c.Sharees = sharees
  125. collections = append(collections, c)
  126. collectionIDs = append(collectionIDs, c.ID)
  127. }
  128. urlMap, err := repo.PublicCollectionRepo.GetCollectionToActivePublicURLMap(context.Background(), collectionIDs)
  129. if err != nil {
  130. return nil, stacktrace.Propagate(err, "failed to get publicURL info")
  131. }
  132. for _, c := range collections {
  133. c.PublicURLs = urlMap[c.ID]
  134. result = append(result, c)
  135. }
  136. return result, nil
  137. }
  138. func (repo *CollectionRepository) GetCollectionsOwnedByUserV2(userID int64, updationTime int64, app ente.App) ([]ente.Collection, error) {
  139. rows, err := repo.DB.Query(`
  140. SELECT
  141. c.collection_id, c.owner_id, c.encrypted_key,c.key_decryption_nonce, c.name, c.encrypted_name, c.name_decryption_nonce, c.type, c.app, c.attributes, c.updation_time, c.is_deleted, c.magic_metadata, c.pub_magic_metadata,
  142. users.user_id, users.encrypted_email, users.email_decryption_nonce, cs.role_type,
  143. pct.access_token, pct.valid_till, pct.device_limit, pct.created_at, pct.updated_at, pct.pw_hash, pct.pw_nonce, pct.mem_limit, pct.ops_limit, pct.enable_download, pct.enable_collect
  144. FROM collections c
  145. LEFT JOIN collection_shares cs
  146. ON (cs.collection_id = c.collection_id AND cs.is_deleted = false)
  147. LEFT JOIN users
  148. ON (cs.to_user_id = users.user_id AND users.encrypted_email IS NOT NULL)
  149. LEFT JOIN public_collection_tokens pct
  150. ON (pct.collection_id = c.collection_id and pct.is_disabled=FALSE)
  151. WHERE c.owner_id = $1 AND c.updation_time > $2 and c.app = $3`, userID, updationTime, string(app))
  152. if err != nil {
  153. return nil, stacktrace.Propagate(err, "")
  154. }
  155. defer rows.Close()
  156. collectionIDToValMap := map[int64]*ente.Collection{}
  157. addPublicUrlMap := map[string]bool{}
  158. result := make([]ente.Collection, 0)
  159. for rows.Next() {
  160. var c ente.Collection
  161. var name, encryptedName, nameDecryptionNonce sql.NullString
  162. var pctDeviceLimit sql.NullInt32
  163. var pctEnableDownload, pctEnableCollect sql.NullBool
  164. var shareUserID, pctValidTill, pctCreatedAt, pctUpdatedAt, pctMemLimit, pctOpsLimit sql.NullInt64
  165. var encryptedEmail, nonce []byte
  166. var shareeRoleType, pctToken, pctPwHash, pctPwNonce sql.NullString
  167. if err := rows.Scan(&c.ID, &c.Owner.ID, &c.EncryptedKey, &c.KeyDecryptionNonce, &name, &encryptedName, &nameDecryptionNonce, &c.Type, &c.App, &c.Attributes, &c.UpdationTime, &c.IsDeleted, &c.MagicMetadata, &c.PublicMagicMetadata,
  168. &shareUserID, &encryptedEmail, &nonce, &shareeRoleType,
  169. &pctToken, &pctValidTill, &pctDeviceLimit, &pctCreatedAt, &pctUpdatedAt, &pctPwHash, &pctPwNonce, &pctMemLimit, &pctOpsLimit, &pctEnableDownload, &pctEnableCollect); err != nil {
  170. return nil, stacktrace.Propagate(err, "")
  171. }
  172. if _, ok := collectionIDToValMap[c.ID]; !ok {
  173. if name.Valid && len(name.String) > 0 {
  174. c.Name = name.String
  175. } else {
  176. c.EncryptedName = encryptedName.String
  177. c.NameDecryptionNonce = nameDecryptionNonce.String
  178. }
  179. c.Sharees = make([]ente.CollectionUser, 0)
  180. c.PublicURLs = make([]ente.PublicURL, 0)
  181. collectionIDToValMap[c.ID] = &c
  182. }
  183. currentCollection := collectionIDToValMap[c.ID]
  184. if shareUserID.Valid {
  185. sharedUser := ente.CollectionUser{
  186. ID: shareUserID.Int64,
  187. Role: ente.ConvertStringToCollectionParticipantRole(shareeRoleType.String),
  188. }
  189. email, err := crypto.Decrypt(encryptedEmail, repo.SecretEncryptionKey, nonce)
  190. if err != nil {
  191. return nil, stacktrace.Propagate(err, "")
  192. }
  193. sharedUser.Email = email
  194. currentCollection.Sharees = append(currentCollection.Sharees, sharedUser)
  195. }
  196. if pctToken.Valid {
  197. if _, ok := addPublicUrlMap[pctToken.String]; !ok {
  198. addPublicUrlMap[pctToken.String] = true
  199. url := ente.PublicURL{
  200. URL: repo.PublicCollectionRepo.GetAlbumUrl(pctToken.String),
  201. DeviceLimit: int(pctDeviceLimit.Int32),
  202. ValidTill: pctValidTill.Int64,
  203. EnableDownload: pctEnableDownload.Bool,
  204. EnableCollect: pctEnableCollect.Bool,
  205. PasswordEnabled: pctPwNonce.Valid,
  206. }
  207. if pctPwNonce.Valid {
  208. url.Nonce = &pctPwNonce.String
  209. url.MemLimit = &pctMemLimit.Int64
  210. url.OpsLimit = &pctOpsLimit.Int64
  211. }
  212. currentCollection.PublicURLs = append(currentCollection.PublicURLs, url)
  213. }
  214. }
  215. }
  216. for _, collection := range collectionIDToValMap {
  217. result = append(result, *collection)
  218. }
  219. return result, nil
  220. }
  221. // GetCollectionsSharedWithUser returns the list of collections that are shared
  222. // with a user
  223. func (repo *CollectionRepository) GetCollectionsSharedWithUser(userID int64, updationTime int64, app ente.App) ([]ente.Collection, error) {
  224. rows, err := repo.DB.Query(`
  225. SELECT collections.collection_id, collections.owner_id, users.encrypted_email, users.email_decryption_nonce, collection_shares.encrypted_key, collections.name, collections.encrypted_name, collections.name_decryption_nonce, collections.type, collections.app, collections.pub_magic_metadata, collection_shares.magic_metadata, collections.updation_time, collection_shares.is_deleted
  226. FROM collections
  227. INNER JOIN users
  228. ON collections.owner_id = users.user_id
  229. INNER JOIN collection_shares
  230. ON collections.collection_id = collection_shares.collection_id AND collection_shares.to_user_id = $1 AND (collection_shares.updation_time > $2 OR collections.updation_time > $2) AND users.encrypted_email IS NOT NULL AND app = $3`, userID, updationTime, string(app))
  231. if err != nil {
  232. return nil, stacktrace.Propagate(err, "")
  233. }
  234. defer rows.Close()
  235. collections := make([]ente.Collection, 0)
  236. for rows.Next() {
  237. var c ente.Collection
  238. var collectionName, encryptedName, nameDecryptionNonce sql.NullString
  239. var encryptedEmail, emailDecryptionNonce []byte
  240. if err := rows.Scan(&c.ID, &c.Owner.ID, &encryptedEmail, &emailDecryptionNonce, &c.EncryptedKey, &collectionName, &encryptedName, &nameDecryptionNonce, &c.Type, &c.App, &c.PublicMagicMetadata, &c.SharedMagicMetadata, &c.UpdationTime, &c.IsDeleted); err != nil {
  241. return collections, stacktrace.Propagate(err, "")
  242. }
  243. if collectionName.Valid && len(collectionName.String) > 0 {
  244. c.Name = collectionName.String
  245. } else {
  246. c.EncryptedName = encryptedName.String
  247. c.NameDecryptionNonce = nameDecryptionNonce.String
  248. }
  249. // if collection is unshared, no need to parse owner's email. Email decryption will fail if the owner's account is deleted
  250. if c.IsDeleted {
  251. c.Owner.Email = ""
  252. } else {
  253. email, err := crypto.Decrypt(encryptedEmail, repo.SecretEncryptionKey, emailDecryptionNonce)
  254. if err != nil {
  255. return collections, stacktrace.Propagate(err, "failed to decrypt email")
  256. }
  257. c.Owner.Email = email
  258. }
  259. // TODO: Pull this information in the previous query
  260. if c.IsDeleted {
  261. // if collection is deleted or unshared, c.IsDeleted will be true. In both cases, we should not send
  262. // back information about other sharees
  263. c.Sharees = make([]ente.CollectionUser, 0)
  264. } else {
  265. sharees, err := repo.GetSharees(c.ID)
  266. if err != nil {
  267. return collections, stacktrace.Propagate(err, "")
  268. }
  269. c.Sharees = sharees
  270. }
  271. collections = append(collections, c)
  272. }
  273. return collections, nil
  274. }
  275. // GetCollectionIDsSharedWithUser returns the list of collections that a user has access to
  276. func (repo *CollectionRepository) GetCollectionIDsSharedWithUser(userID int64) ([]int64, error) {
  277. rows, err := repo.DB.Query(`
  278. SELECT collection_id
  279. FROM collection_shares
  280. WHERE collection_shares.to_user_id = $1
  281. AND collection_shares.is_deleted = $2`, userID, false)
  282. if err != nil {
  283. return nil, stacktrace.Propagate(err, "")
  284. }
  285. defer rows.Close()
  286. cIDs := make([]int64, 0)
  287. for rows.Next() {
  288. var cID int64
  289. if err := rows.Scan(&cID); err != nil {
  290. return cIDs, stacktrace.Propagate(err, "")
  291. }
  292. cIDs = append(cIDs, cID)
  293. }
  294. return cIDs, nil
  295. }
  296. // GetCollectionIDsOwnedByUser returns the map of collectionID (owned by user) to collection deletion status
  297. func (repo *CollectionRepository) GetCollectionIDsOwnedByUser(userID int64) (map[int64]bool, error) {
  298. rows, err := repo.DB.Query(`
  299. SELECT collection_id, is_deleted
  300. FROM collections
  301. WHERE owner_id = $1
  302. AND is_deleted = $2`, userID, false)
  303. if err != nil {
  304. return nil, stacktrace.Propagate(err, "")
  305. }
  306. defer rows.Close()
  307. result := make(map[int64]bool, 0)
  308. for rows.Next() {
  309. var cID int64
  310. var isDeleted bool
  311. if err := rows.Scan(&cID, &isDeleted); err != nil {
  312. return result, stacktrace.Propagate(err, "")
  313. }
  314. result[cID] = isDeleted
  315. }
  316. return result, nil
  317. }
  318. // GetAllSharedCollections returns list of SharedCollection in which the given user is involed
  319. func (repo *CollectionRepository) GetAllSharedCollections(ctx context.Context, userID int64) ([]SharedCollection, error) {
  320. rows, err := repo.DB.QueryContext(ctx, `SELECT collection_id, to_user_id, from_user_id
  321. FROM collection_shares
  322. WHERE (to_user_id = $1 or from_user_id = $1)
  323. AND is_deleted = $2`, userID, false)
  324. if err != nil {
  325. return nil, stacktrace.Propagate(err, "")
  326. }
  327. defer rows.Close()
  328. result := make([]SharedCollection, 0)
  329. for rows.Next() {
  330. logrus.Info("reading row")
  331. var sharedCollection SharedCollection
  332. if err := rows.Scan(&sharedCollection.CollectionID, &sharedCollection.ToUserID, &sharedCollection.FromUserID); err != nil {
  333. logrus.WithError(err).Info("failed to scan")
  334. return result, stacktrace.Propagate(err, "")
  335. }
  336. result = append(result, sharedCollection)
  337. }
  338. return result, nil
  339. }
  340. // DoesFileExistInCollections returns true if the file exists in one of the
  341. // provided collections
  342. func (repo *CollectionRepository) DoesFileExistInCollections(fileID int64, cIDs []int64) (bool, error) {
  343. var exists bool
  344. err := repo.DB.QueryRow(`SELECT EXISTS (SELECT 1 FROM collection_files WHERE file_id = $1 AND is_deleted = $2 AND collection_id = ANY ($3))`,
  345. fileID, false, pq.Array(cIDs)).Scan(&exists)
  346. return exists, stacktrace.Propagate(err, "")
  347. }
  348. // VerifyAllFileIDsExistsInCollection returns error if the fileIDs don't exist in the collection
  349. func (repo *CollectionRepository) VerifyAllFileIDsExistsInCollection(ctx context.Context, cID int64, fileIDs []int64) error {
  350. fileIdMap := make(map[int64]bool)
  351. rows, err := repo.DB.QueryContext(ctx, `SELECT file_id FROM collection_files WHERE collection_id = $1 AND is_deleted = $2 AND file_id = ALL ($3)`,
  352. cID, false, pq.Array(fileIDs))
  353. if err != nil {
  354. return stacktrace.Propagate(err, "")
  355. }
  356. for rows.Next() {
  357. var fileID int64
  358. if err := rows.Scan(&fileID); err != nil {
  359. return stacktrace.Propagate(err, "")
  360. }
  361. fileIdMap[fileID] = true
  362. }
  363. // find fileIds that are not present in the collection
  364. for _, fileID := range fileIDs {
  365. if _, ok := fileIdMap[fileID]; !ok {
  366. return stacktrace.Propagate(fmt.Errorf("fileID %d not found in collection %d", fileID, cID), "")
  367. }
  368. }
  369. return nil
  370. }
  371. // GetCollectionShareeRole returns true if the collection is shared with the user
  372. func (repo *CollectionRepository) GetCollectionShareeRole(cID int64, userID int64) (*ente.CollectionParticipantRole, error) {
  373. var role *ente.CollectionParticipantRole
  374. err := repo.DB.QueryRow(`(SELECT role_type FROM collection_shares WHERE collection_id = $1 AND to_user_id = $2 AND is_deleted = $3)`,
  375. cID, userID, false).Scan(&role)
  376. return role, stacktrace.Propagate(err, "")
  377. }
  378. func (repo *CollectionRepository) GetOwnerID(collectionID int64) (int64, error) {
  379. row := repo.DB.QueryRow(`SELECT owner_id FROM collections WHERE collection_id = $1`, collectionID)
  380. var ownerID int64
  381. err := row.Scan(&ownerID)
  382. return ownerID, stacktrace.Propagate(err, "failed to get collection owner")
  383. }
  384. // GetCollectionsFilesCount returns the number of non-deleted files which are present in the given collection
  385. func (repo *CollectionRepository) GetCollectionsFilesCount(collectionID int64) (int64, error) {
  386. row := repo.DB.QueryRow(`SELECT count(*) FROM collection_files WHERE collection_id=$1 AND is_deleted = false`, collectionID)
  387. var count int64 = 0
  388. err := row.Scan(&count)
  389. if err != nil {
  390. return -1, stacktrace.Propagate(err, "")
  391. }
  392. return count, nil
  393. }
  394. // Share shares a collection with a userID
  395. func (repo *CollectionRepository) Share(
  396. collectionID int64,
  397. fromUserID int64,
  398. toUserID int64,
  399. encryptedKey string,
  400. role ente.CollectionParticipantRole,
  401. updationTime int64) error {
  402. context := context.Background()
  403. tx, err := repo.DB.BeginTx(context, nil)
  404. if err != nil {
  405. return stacktrace.Propagate(err, "")
  406. }
  407. if role != ente.VIEWER && role != ente.COLLABORATOR {
  408. err = fmt.Errorf("invalid role %s", string(role))
  409. return stacktrace.Propagate(err, "")
  410. }
  411. _, err = tx.ExecContext(context, `INSERT INTO collection_shares(collection_id, from_user_id, to_user_id, encrypted_key, updation_time, role_type) VALUES($1, $2, $3, $4, $5, $6)
  412. ON CONFLICT (collection_id, from_user_id, to_user_id)
  413. DO UPDATE SET(is_deleted, updation_time, role_type) = (FALSE, $5, $6)`,
  414. collectionID, fromUserID, toUserID, encryptedKey, updationTime, role)
  415. if err != nil {
  416. tx.Rollback()
  417. return stacktrace.Propagate(err, "")
  418. }
  419. _, err = tx.ExecContext(context, `UPDATE collections SET updation_time = $1 WHERE collection_id = $2`, updationTime, collectionID)
  420. if err != nil {
  421. tx.Rollback()
  422. return stacktrace.Propagate(err, "")
  423. }
  424. err = tx.Commit()
  425. return stacktrace.Propagate(err, "")
  426. }
  427. // UpdateShareeMetadata shares a collection with a userID
  428. func (repo *CollectionRepository) UpdateShareeMetadata(
  429. collectionID int64,
  430. ownerUserID int64,
  431. shareeUserID int64,
  432. metadata ente.MagicMetadata,
  433. updationTime int64) error {
  434. context := context.Background()
  435. tx, err := repo.DB.BeginTx(context, nil)
  436. if err != nil {
  437. return stacktrace.Propagate(err, "")
  438. }
  439. // Update collection_shares metadata if the collection is not deleted
  440. sqlResult, err := tx.ExecContext(context, `UPDATE collection_shares SET magic_metadata = $1, updation_time = $2 WHERE collection_id = $3 AND from_user_id = $4 AND to_user_id = $5 AND is_deleted = $6`,
  441. metadata, updationTime, collectionID, ownerUserID, shareeUserID, false)
  442. if err != nil {
  443. tx.Rollback()
  444. return stacktrace.Propagate(err, "")
  445. }
  446. // verify that only one row is affected
  447. affected, err := sqlResult.RowsAffected()
  448. if err != nil {
  449. tx.Rollback()
  450. return stacktrace.Propagate(err, "")
  451. }
  452. if affected != 1 {
  453. tx.Rollback()
  454. err = fmt.Errorf("invalid number of rows affected %d", affected)
  455. return stacktrace.Propagate(err, "")
  456. }
  457. _, err = tx.ExecContext(context, `UPDATE collections SET updation_time = $1 WHERE collection_id = $2`, updationTime, collectionID)
  458. if err != nil {
  459. tx.Rollback()
  460. return stacktrace.Propagate(err, "")
  461. }
  462. err = tx.Commit()
  463. return stacktrace.Propagate(err, "")
  464. }
  465. // UnShare un-shares a collection from a userID
  466. func (repo *CollectionRepository) UnShare(collectionID int64, toUserID int64) error {
  467. updationTime := time.Microseconds()
  468. context := context.Background()
  469. tx, err := repo.DB.BeginTx(context, nil)
  470. if err != nil {
  471. return stacktrace.Propagate(err, "")
  472. }
  473. _, err = tx.ExecContext(context, `UPDATE collection_shares
  474. SET is_deleted = $1, updation_time = $2
  475. WHERE collection_id = $3 AND to_user_id = $4`, true, updationTime, collectionID, toUserID)
  476. if err != nil {
  477. tx.Rollback()
  478. return stacktrace.Propagate(err, "")
  479. }
  480. // remove all the files which were added by this user
  481. // todo: should we also add c_owner_id != toUserId
  482. _, err = tx.ExecContext(context, `UPDATE collection_files
  483. SET is_deleted = $1, updation_time = $2
  484. WHERE collection_id = $3 AND f_owner_id = $4`, true, updationTime, collectionID, toUserID)
  485. if err != nil {
  486. tx.Rollback()
  487. return stacktrace.Propagate(err, "")
  488. }
  489. _, err = tx.ExecContext(context, `UPDATE collections SET updation_time = $1
  490. WHERE collection_id = $2`, updationTime, collectionID)
  491. if err != nil {
  492. tx.Rollback()
  493. return stacktrace.Propagate(err, "")
  494. }
  495. err = tx.Commit()
  496. return stacktrace.Propagate(err, "")
  497. }
  498. // AddFiles adds files to a collection
  499. func (repo *CollectionRepository) AddFiles(
  500. collectionID int64,
  501. collectionOwnerID int64,
  502. files []ente.CollectionFileItem,
  503. fileOwnerID int64,
  504. ) error {
  505. updationTime := time.Microseconds()
  506. context := context.Background()
  507. tx, err := repo.DB.BeginTx(context, nil)
  508. if err != nil {
  509. return stacktrace.Propagate(err, "")
  510. }
  511. for _, file := range files {
  512. _, err := tx.ExecContext(context, `INSERT INTO collection_files
  513. (collection_id, file_id, encrypted_key, key_decryption_nonce, is_deleted, updation_time, c_owner_id, f_owner_id)
  514. VALUES($1, $2, $3, $4, $5, $6, $7, $8)
  515. ON CONFLICT ON CONSTRAINT unique_collection_files_cid_fid
  516. DO UPDATE SET(is_deleted, updation_time) = ($5, $6)`, collectionID, file.ID, file.EncryptedKey,
  517. file.KeyDecryptionNonce, false, updationTime, collectionOwnerID, fileOwnerID)
  518. if err != nil {
  519. tx.Rollback()
  520. return stacktrace.Propagate(err, "")
  521. }
  522. }
  523. _, err = tx.ExecContext(context, `UPDATE collections SET updation_time = $1
  524. WHERE collection_id = $2`, updationTime, collectionID)
  525. if err != nil {
  526. tx.Rollback()
  527. return stacktrace.Propagate(err, "")
  528. }
  529. err = tx.Commit()
  530. return stacktrace.Propagate(err, "")
  531. }
  532. func (repo *CollectionRepository) RestoreFiles(ctx context.Context, userID int64, collectionID int64, newCollectionFiles []ente.CollectionFileItem) error {
  533. fileIDs := make([]int64, 0)
  534. for _, newFile := range newCollectionFiles {
  535. fileIDs = append(fileIDs, newFile.ID)
  536. }
  537. // verify that all files are restorable
  538. _, canRestoreAllFiles, err := repo.TrashRepo.GetFilesInTrashState(ctx, userID, fileIDs)
  539. if err != nil {
  540. return stacktrace.Propagate(err, "")
  541. }
  542. if !canRestoreAllFiles {
  543. return stacktrace.Propagate(ente.ErrBadRequest, "some fileIDs are not restorable")
  544. }
  545. tx, err := repo.DB.BeginTx(ctx, nil)
  546. updationTime := time.Microseconds()
  547. if err != nil {
  548. return stacktrace.Propagate(err, "")
  549. }
  550. for _, file := range newCollectionFiles {
  551. _, err := tx.ExecContext(ctx, `INSERT INTO collection_files
  552. (collection_id, file_id, encrypted_key, key_decryption_nonce, is_deleted, updation_time, c_owner_id, f_owner_id)
  553. VALUES($1, $2, $3, $4, $5, $6, $7, $8)
  554. ON CONFLICT ON CONSTRAINT unique_collection_files_cid_fid
  555. DO UPDATE SET(is_deleted, updation_time) = ($5, $6)`, collectionID, file.ID, file.EncryptedKey,
  556. file.KeyDecryptionNonce, false, updationTime, userID, userID)
  557. if err != nil {
  558. tx.Rollback()
  559. return stacktrace.Propagate(err, "")
  560. }
  561. }
  562. _, err = tx.ExecContext(ctx, `UPDATE collections SET updation_time = $1
  563. WHERE collection_id = $2`, updationTime, collectionID)
  564. if err != nil {
  565. tx.Rollback()
  566. return stacktrace.Propagate(err, "")
  567. }
  568. _, err = tx.ExecContext(ctx, `UPDATE trash SET is_restored = true
  569. WHERE user_id = $1 and file_id = ANY ($2)`, userID, pq.Array(fileIDs))
  570. if err != nil {
  571. tx.Rollback()
  572. return stacktrace.Propagate(err, "")
  573. }
  574. return tx.Commit()
  575. }
  576. // RemoveFilesV3 just remove the entries from the collection. This method assume that collection owner is
  577. // different from the file owners
  578. func (repo *CollectionRepository) RemoveFilesV3(context context.Context, collectionID int64, fileIDs []int64) error {
  579. updationTime := time.Microseconds()
  580. tx, err := repo.DB.BeginTx(context, nil)
  581. if err != nil {
  582. return stacktrace.Propagate(err, "")
  583. }
  584. _, err = tx.ExecContext(context, `UPDATE collection_files
  585. SET is_deleted = $1, updation_time = $2 WHERE collection_id = $3 AND file_id = ANY($4)`,
  586. true, updationTime, collectionID, pq.Array(fileIDs))
  587. if err != nil {
  588. tx.Rollback()
  589. return stacktrace.Propagate(err, "")
  590. }
  591. _, err = tx.ExecContext(context, `UPDATE collections SET updation_time = $1
  592. WHERE collection_id = $2`, updationTime, collectionID)
  593. if err != nil {
  594. tx.Rollback()
  595. return stacktrace.Propagate(err, "")
  596. }
  597. err = tx.Commit()
  598. return stacktrace.Propagate(err, "")
  599. }
  600. // MoveFiles move files from one collection to another collection
  601. func (repo *CollectionRepository) MoveFiles(ctx context.Context,
  602. toCollectionID int64, fromCollectionID int64,
  603. fileItems []ente.CollectionFileItem,
  604. collectionOwner int64,
  605. fileOwner int64,
  606. ) error {
  607. if collectionOwner != fileOwner {
  608. return fmt.Errorf("move is not supported when collection and file onwer are different")
  609. }
  610. updationTime := time.Microseconds()
  611. tx, err := repo.DB.BeginTx(ctx, nil)
  612. if err != nil {
  613. return stacktrace.Propagate(err, "")
  614. }
  615. fileIDs := make([]int64, 0)
  616. for _, file := range fileItems {
  617. fileIDs = append(fileIDs, file.ID)
  618. _, err := tx.ExecContext(ctx, `INSERT INTO collection_files
  619. (collection_id, file_id, encrypted_key, key_decryption_nonce, is_deleted, updation_time, c_owner_id, f_owner_id)
  620. VALUES($1, $2, $3, $4, $5, $6, $7, $8)
  621. ON CONFLICT ON CONSTRAINT unique_collection_files_cid_fid
  622. DO UPDATE SET(is_deleted, updation_time) = ($5, $6)`, toCollectionID, file.ID, file.EncryptedKey,
  623. file.KeyDecryptionNonce, false, updationTime, collectionOwner, fileOwner)
  624. if err != nil {
  625. if rollbackErr := tx.Rollback(); rollbackErr != nil {
  626. logrus.WithError(rollbackErr).Error("transaction rollback failed")
  627. return stacktrace.Propagate(rollbackErr, "")
  628. }
  629. return stacktrace.Propagate(err, "")
  630. }
  631. }
  632. _, err = tx.ExecContext(ctx, `UPDATE collection_files
  633. SET is_deleted = $1, updation_time = $2 WHERE collection_id = $3 AND file_id = ANY($4)`,
  634. true, updationTime, fromCollectionID, pq.Array(fileIDs))
  635. if err != nil {
  636. if rollbackErr := tx.Rollback(); rollbackErr != nil {
  637. logrus.WithError(rollbackErr).Error("transaction rollback failed")
  638. return stacktrace.Propagate(rollbackErr, "")
  639. }
  640. return stacktrace.Propagate(err, "")
  641. }
  642. _, err = tx.ExecContext(ctx, `UPDATE collections SET updation_time = $1
  643. WHERE (collection_id = $2 or collection_id = $3 )`, updationTime, toCollectionID, fromCollectionID)
  644. if err != nil {
  645. if rollbackErr := tx.Rollback(); rollbackErr != nil {
  646. logrus.WithError(rollbackErr).Error("transaction rollback failed")
  647. return stacktrace.Propagate(rollbackErr, "")
  648. }
  649. return stacktrace.Propagate(err, "")
  650. }
  651. return tx.Commit()
  652. }
  653. // GetDiff returns the diff of files added or modified within a collection since
  654. // the specified time
  655. func (repo *CollectionRepository) GetDiff(collectionID int64, sinceTime int64, limit int) ([]ente.File, error) {
  656. startTime := t.Now()
  657. defer func() {
  658. repo.LatencyLogger.WithLabelValues("CollectionRepo.GetDiff").
  659. Observe(float64(t.Since(startTime).Milliseconds()))
  660. }()
  661. rows, err := repo.DB.Query(`
  662. SELECT files.file_id, files.owner_id, collection_files.collection_id, collection_files.c_owner_id,
  663. collection_files.encrypted_key, collection_files.key_decryption_nonce,
  664. files.file_decryption_header, files.thumbnail_decryption_header,
  665. files.metadata_decryption_header, files.encrypted_metadata, files.magic_metadata, files.pub_magic_metadata,
  666. files.info, collection_files.is_deleted, collection_files.updation_time
  667. FROM files
  668. INNER JOIN collection_files
  669. ON collection_files.file_id = files.file_id
  670. AND collection_files.collection_id = $1
  671. AND collection_files.updation_time > $2
  672. ORDER BY collection_files.updation_time LIMIT $3`,
  673. collectionID, sinceTime, limit)
  674. if err != nil {
  675. return nil, stacktrace.Propagate(err, "")
  676. }
  677. return convertRowsToFiles(rows)
  678. }
  679. func (repo *CollectionRepository) GetFilesWithVersion(collectionID int64, updateAtTime int64) ([]ente.File, error) {
  680. startTime := t.Now()
  681. defer func() {
  682. repo.LatencyLogger.WithLabelValues("CollectionRepo.GetFilesWithVersion").
  683. Observe(float64(t.Since(startTime).Milliseconds()))
  684. }()
  685. rows, err := repo.DB.Query(`
  686. SELECT files.file_id, files.owner_id, collection_files.collection_id, collection_files.c_owner_id,
  687. collection_files.encrypted_key, collection_files.key_decryption_nonce,
  688. files.file_decryption_header, files.thumbnail_decryption_header,
  689. files.metadata_decryption_header, files.encrypted_metadata, files.magic_metadata, files.pub_magic_metadata,
  690. files.info, collection_files.is_deleted, collection_files.updation_time
  691. FROM files
  692. INNER JOIN collection_files
  693. ON collection_files.file_id = files.file_id
  694. AND collection_files.collection_id = $1
  695. AND collection_files.updation_time = $2`,
  696. collectionID, updateAtTime)
  697. if err != nil {
  698. return nil, stacktrace.Propagate(err, "")
  699. }
  700. return convertRowsToFiles(rows)
  701. }
  702. func (repo *CollectionRepository) GetFile(collectionID int64, fileID int64) ([]ente.File, error) {
  703. rows, err := repo.DB.Query(`
  704. SELECT files.file_id, files.owner_id, collection_files.collection_id, collection_files.c_owner_id,
  705. collection_files.encrypted_key, collection_files.key_decryption_nonce,
  706. files.file_decryption_header, files.thumbnail_decryption_header,
  707. files.metadata_decryption_header, files.encrypted_metadata, files.magic_metadata, files.pub_magic_metadata,
  708. files.info, collection_files.is_deleted, collection_files.updation_time
  709. FROM files
  710. INNER JOIN collection_files
  711. ON collection_files.file_id = files.file_id
  712. AND collection_files.collection_id = $1
  713. AND collection_files.file_id = $2`,
  714. collectionID, fileID)
  715. if err != nil {
  716. return nil, stacktrace.Propagate(err, "")
  717. }
  718. files, err := convertRowsToFiles(rows)
  719. if err != nil {
  720. return nil, stacktrace.Propagate(err, "")
  721. }
  722. return files, nil
  723. }
  724. // GetSharees returns the list of users a collection has been shared with
  725. func (repo *CollectionRepository) GetSharees(cID int64) ([]ente.CollectionUser, error) {
  726. rows, err := repo.DB.Query(`
  727. SELECT users.user_id, users.encrypted_email, users.email_decryption_nonce, collection_shares.role_type
  728. FROM users
  729. INNER JOIN collection_shares
  730. ON (collection_shares.collection_id = $1 AND collection_shares.to_user_id = users.user_id AND collection_shares.is_deleted = $2 AND users.encrypted_email IS NOT NULL)`,
  731. cID, false)
  732. if err != nil {
  733. return nil, stacktrace.Propagate(err, "")
  734. }
  735. defer rows.Close()
  736. users := make([]ente.CollectionUser, 0)
  737. for rows.Next() {
  738. var user ente.CollectionUser
  739. var encryptedEmail, nonce []byte
  740. if err := rows.Scan(&user.ID, &encryptedEmail, &nonce, &user.Role); err != nil {
  741. return users, stacktrace.Propagate(err, "")
  742. }
  743. email, err := crypto.Decrypt(encryptedEmail, repo.SecretEncryptionKey, nonce)
  744. if err != nil {
  745. return users, stacktrace.Propagate(err, "")
  746. }
  747. user.Email = email
  748. users = append(users, user)
  749. }
  750. return users, nil
  751. }
  752. // GetCollectionFileIDs return list of fileIDs are currently present in the given collection
  753. // and fileIDs are owned by the collection owner
  754. func (repo *CollectionRepository) GetCollectionFileIDs(collectionID int64, collectionOwnerID int64) ([]int64, error) {
  755. // Collaboration Todo: Filter out files which are not owned by the collection owner
  756. rows, err := repo.DB.Query(
  757. `SELECT file_id
  758. FROM collection_files
  759. WHERE is_deleted=false
  760. AND collection_id =$1 AND (f_owner_id is null or f_owner_id = $2)`, collectionID, collectionOwnerID)
  761. if err != nil {
  762. return make([]int64, 0), stacktrace.Propagate(err, "")
  763. }
  764. return convertRowsToFileId(rows)
  765. }
  766. func convertRowsToFileId(rows *sql.Rows) ([]int64, error) {
  767. fileIDs := make([]int64, 0)
  768. defer rows.Close()
  769. for rows.Next() {
  770. var fileID int64
  771. if err := rows.Scan(&fileID); err != nil {
  772. return fileIDs, stacktrace.Propagate(err, "")
  773. }
  774. fileIDs = append(fileIDs, fileID)
  775. }
  776. return fileIDs, nil
  777. }
  778. // TrashV3 move the files belonging to the collection owner to the trash
  779. func (repo *CollectionRepository) TrashV3(ctx context.Context, collectionID int64) error {
  780. log := logrus.WithFields(logrus.Fields{
  781. "deleting_collection": collectionID,
  782. })
  783. collection, err := repo.Get(collectionID)
  784. if err != nil {
  785. log.WithError(err).Error("failed to get collection")
  786. return stacktrace.Propagate(err, "")
  787. }
  788. ownerID := collection.Owner.ID
  789. fileIDs, err := repo.GetCollectionFileIDs(collectionID, ownerID)
  790. if err != nil {
  791. log.WithError(err).Error("failed to get fileIDs")
  792. return stacktrace.Propagate(err, "")
  793. }
  794. log.WithField("file_count", len(fileIDs)).Info("Fetched fileIDs")
  795. batchSize := 2000
  796. for i := 0; i < len(fileIDs); i += batchSize {
  797. end := i + batchSize
  798. if end > len(fileIDs) {
  799. end = len(fileIDs)
  800. }
  801. batch := fileIDs[i:end]
  802. err := repo.FileRepo.VerifyFileOwner(ctx, batch, ownerID, log)
  803. if err != nil {
  804. return stacktrace.Propagate(err, "")
  805. }
  806. items := make([]ente.TrashItemRequest, 0)
  807. for _, fileID := range batch {
  808. items = append(items, ente.TrashItemRequest{
  809. FileID: fileID,
  810. CollectionID: collectionID,
  811. })
  812. }
  813. err = repo.TrashRepo.TrashFiles(fileIDs, ownerID, ente.TrashRequest{OwnerID: ownerID, TrashItems: items})
  814. if err != nil {
  815. log.WithError(err).Error("failed to trash file")
  816. return stacktrace.Propagate(err, "")
  817. }
  818. }
  819. // Verify that all files are processed in the collection.
  820. count, err := repo.GetCollectionsFilesCount(collectionID)
  821. if err != nil {
  822. return stacktrace.Propagate(err, "")
  823. }
  824. if count != 0 {
  825. removedFiles, removeErr := repo.removeAllFilesAddedByOthers(collectionID)
  826. if removeErr != nil {
  827. return stacktrace.Propagate(removeErr, "")
  828. }
  829. if count != removedFiles {
  830. return fmt.Errorf("investigate: collection %d still has %d files which are not deleted", collectionID, removedFiles-count)
  831. } else {
  832. logrus.WithField("collection_id", collectionID).
  833. WithField("file_count", count).
  834. WithField("removed_files", removedFiles).
  835. Info("All files are removed from the collection")
  836. return nil
  837. }
  838. }
  839. return nil
  840. }
  841. func (repo *CollectionRepository) removeAllFilesAddedByOthers(collectionID int64) (int64, error) {
  842. var fileIDs []int64
  843. rows, err := repo.DB.Query(`SELECT file_id FROM collection_files WHERE collection_id = $1 AND is_deleted=false AND f_owner_id IS NOT NULL AND c_owner_id IS NOT NULL AND f_owner_id <> c_owner_id`, collectionID)
  844. if err != nil {
  845. return 0, stacktrace.Propagate(err, "")
  846. }
  847. defer rows.Close()
  848. for rows.Next() {
  849. var fileID int64
  850. if err := rows.Scan(&fileID); err != nil {
  851. return 0, stacktrace.Propagate(err, "")
  852. }
  853. fileIDs = append(fileIDs, fileID)
  854. }
  855. if len(fileIDs) == 0 {
  856. return 0, nil
  857. }
  858. removeErr := repo.RemoveFilesV3(context.Background(), collectionID, fileIDs)
  859. if removeErr != nil {
  860. return 0, stacktrace.Propagate(removeErr, "")
  861. }
  862. return int64(len(fileIDs)), nil
  863. }
  864. // ScheduleDelete marks the collection as deleted and queue up an operation to
  865. // move the collection files to user's trash.
  866. // See [Collection Delete Versions] for more details
  867. func (repo *CollectionRepository) ScheduleDelete(collectionID int64) error {
  868. updationTime := time.Microseconds()
  869. ctx := context.Background()
  870. tx, err := repo.DB.BeginTx(ctx, nil)
  871. if err != nil {
  872. return stacktrace.Propagate(err, "")
  873. }
  874. _, err = tx.ExecContext(ctx, `UPDATE collection_shares
  875. SET is_deleted = $1, updation_time = $2
  876. WHERE collection_id = $3`, true, updationTime, collectionID)
  877. if err != nil {
  878. tx.Rollback()
  879. return stacktrace.Propagate(err, "")
  880. }
  881. _, err = tx.ExecContext(ctx, `UPDATE collections
  882. SET is_deleted = $1, updation_time = $2
  883. WHERE collection_id = $3`, true, updationTime, collectionID)
  884. if err != nil {
  885. tx.Rollback()
  886. return stacktrace.Propagate(err, "")
  887. }
  888. err = repo.QueueRepo.AddItems(ctx, tx, TrashCollectionQueueV3, []string{strconv.FormatInt(collectionID, 10)})
  889. if err != nil {
  890. tx.Rollback()
  891. return stacktrace.Propagate(err, "")
  892. }
  893. err = tx.Commit()
  894. return stacktrace.Propagate(err, "")
  895. }
  896. // Rename updates the collection's name by updating the encrypted_name and name_decryption_nonce of the collection
  897. func (repo *CollectionRepository) Rename(collectionID int64, encryptedName string, nameDecryptionNonce string) error {
  898. updationTime := time.Microseconds()
  899. _, err := repo.DB.Exec(`UPDATE collections
  900. SET encrypted_name = $1,
  901. name_decryption_nonce=$2,
  902. updation_time=$3
  903. WHERE collection_id = $4`,
  904. encryptedName, nameDecryptionNonce, updationTime, collectionID)
  905. return stacktrace.Propagate(err, "")
  906. }
  907. // UpdateMagicMetadata updates the magic metadata for the given collection
  908. func (repo *CollectionRepository) UpdateMagicMetadata(ctx context.Context,
  909. collectionID int64,
  910. magicMetadata ente.MagicMetadata,
  911. isPublicMetadata bool,
  912. ) error {
  913. updationTime := time.Microseconds()
  914. magicMetadata.Version = magicMetadata.Version + 1
  915. var err error
  916. if isPublicMetadata {
  917. _, err = repo.DB.ExecContext(ctx, `UPDATE collections
  918. SET pub_magic_metadata = $1,
  919. updation_time=$2
  920. WHERE collection_id = $3`,
  921. magicMetadata, updationTime, collectionID)
  922. } else {
  923. _, err = repo.DB.ExecContext(ctx, `UPDATE collections
  924. SET magic_metadata = $1,
  925. updation_time=$2
  926. WHERE collection_id = $3`,
  927. magicMetadata, updationTime, collectionID)
  928. }
  929. return stacktrace.Propagate(err, "")
  930. }
  931. func (repo *CollectionRepository) GetSharedCollectionsCount(userID int64) (int64, error) {
  932. row := repo.DB.QueryRow(`SELECT count(*) FROM collection_shares WHERE from_user_id = $1`, userID)
  933. var count int64 = 0
  934. err := row.Scan(&count)
  935. if err != nil {
  936. return -1, stacktrace.Propagate(err, "")
  937. }
  938. return count, nil
  939. }
  940. func (repo *CollectionRepository) GetCollectionCount(fileID int64) (int64, error) {
  941. row := repo.DB.QueryRow(`SELECT count(*) FROM collection_files WHERE file_id = $1 and is_deleted = false`, fileID)
  942. var count int64 = 0
  943. err := row.Scan(&count)
  944. if err != nil {
  945. return -1, stacktrace.Propagate(err, "")
  946. }
  947. return count, nil
  948. }