firestore.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package firestore
  2. import (
  3. "context"
  4. "time"
  5. fs "cloud.google.com/go/firestore"
  6. "github.com/kellegous/go/internal"
  7. "golang.org/x/oauth2/google"
  8. "google.golang.org/grpc/codes"
  9. "google.golang.org/grpc/status"
  10. )
  11. // NextID is the next numeric ID to use for auto-generated IDs
  12. type NextID struct {
  13. ID uint32 `json:"id" firestore:"id"`
  14. }
  15. // Backend provides access to Google Firestore.
  16. type Backend struct {
  17. db *fs.Client
  18. }
  19. // New instantiates a new Backend
  20. func New(ctx context.Context, path string) (*Backend, error) {
  21. if path == "" {
  22. path = getGoogleProject()
  23. }
  24. client, err := fs.NewClient(ctx, path)
  25. if err != nil {
  26. return nil, err
  27. }
  28. backend := Backend{
  29. db: client,
  30. }
  31. return &backend, nil
  32. }
  33. // Close the resources associated with this backend.
  34. func (backend *Backend) Close() error {
  35. return backend.db.Close()
  36. }
  37. // Get retreives a shortcut from the data store.
  38. func (backend *Backend) Get(ctx context.Context, name string) (*internal.Route, error) {
  39. ref := backend.db.Doc("routes/" + name)
  40. snap, err := ref.Get(ctx)
  41. if err != nil {
  42. if status.Code(err) == codes.NotFound {
  43. return nil, internal.ErrRouteNotFound
  44. }
  45. return nil, err
  46. }
  47. var rt internal.Route
  48. if err := snap.DataTo(&rt); err != nil {
  49. return nil, err
  50. }
  51. return &rt, nil
  52. }
  53. // Put stores a new shortcut in the data store.
  54. func (backend *Backend) Put(ctx context.Context, key string, rt *internal.Route) error {
  55. ref := backend.db.Doc("routes/" + key)
  56. _, err := ref.Set(ctx, rt)
  57. if err != nil {
  58. return err
  59. }
  60. return nil
  61. }
  62. // Del removes an existing shortcut from the data store.
  63. func (backend *Backend) Del(ctx context.Context, key string) error {
  64. ref := backend.db.Doc("routes/" + key)
  65. _, err := ref.Delete(ctx)
  66. if err != nil {
  67. return err
  68. }
  69. return nil
  70. }
  71. // List all routes in an iterator, starting with the key prefix of start (which can also be nil).
  72. func (backend *Backend) List(ctx context.Context, start string) (internal.RouteIterator, error) {
  73. col := backend.db.Collection("routes").OrderBy(fs.DocumentID, fs.Asc)
  74. if start != "" {
  75. // we have a starting ID.
  76. col = col.StartAt(start)
  77. }
  78. return &RouteIterator{
  79. ctx: ctx,
  80. db: backend.db,
  81. it: col.Documents(ctx),
  82. }, nil
  83. }
  84. // GetAll gets everything in the db to dump it out for backup purposes
  85. func (backend *Backend) GetAll(ctx context.Context) (map[string]internal.Route, error) {
  86. golinks := map[string]internal.Route{}
  87. col := backend.db.Collection("routes").OrderBy(fs.DocumentID, fs.Asc)
  88. routes, err := col.Documents(ctx).GetAll()
  89. if err != nil {
  90. return nil, err
  91. }
  92. for _, doc := range routes {
  93. var rt internal.Route
  94. if err := doc.DataTo(&rt); err != nil {
  95. return nil, err
  96. }
  97. golinks[doc.Ref.ID] = rt
  98. }
  99. return golinks, nil
  100. }
  101. // NextID generates the next numeric ID to be used for an auto-named shortcut.
  102. func (backend *Backend) NextID(ctx context.Context) (uint64, error) {
  103. ref := backend.db.Doc("IDs/nextID")
  104. var nid uint32
  105. err := backend.db.RunTransaction(ctx, func(ctx context.Context, tx *fs.Transaction) error {
  106. var nextID *NextID
  107. doc, err := tx.Get(ref)
  108. if err != nil {
  109. if status.Code(err) == codes.NotFound {
  110. // this is the very first auto-generated ID, we can make it
  111. // as :1 and return it
  112. nextID = new(NextID)
  113. nextID.ID = 1
  114. nid = 1
  115. err := tx.Create(ref, nextID)
  116. if err != nil {
  117. return err
  118. }
  119. return nil
  120. }
  121. return err
  122. }
  123. if err := doc.DataTo(&nextID); err != nil {
  124. return err
  125. }
  126. nextID.ID += 1
  127. nid = nextID.ID
  128. return tx.Set(ref, &nextID)
  129. })
  130. if err != nil {
  131. return 0, err
  132. }
  133. return uint64(nid), nil
  134. }
  135. func getGoogleProject() string {
  136. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  137. defer cancel()
  138. creds, err := google.FindDefaultCredentials(ctx)
  139. if err != nil {
  140. return ""
  141. }
  142. return creds.ProjectID
  143. }