cache/api.go (view raw)
1package cache
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log"
9 "net/http"
10 "os"
11
12 "path/filepath"
13 "time"
14
15 "golang.org/x/oauth2"
16 "golang.org/x/oauth2/google"
17 "google.golang.org/api/drive/v3"
18 "google.golang.org/api/option"
19 "google.golang.org/api/sheets/v4"
20)
21
22type GoogleAPI struct {
23 Ctx context.Context
24 Client *http.Client
25 spreadsheetId string
26 spreadsheetRange string
27}
28
29type Entry struct {
30 FileID string `json:"id"`
31 Month string `json:"date"`
32 Name string `json:"name"`
33 FilePath string `json:"content"`
34}
35
36// Retrieve a token, saves the token, then returns the generated client.
37func getClient(config *oauth2.Config) *http.Client {
38 // The file token.json stores the user's access and refresh tokens, and is
39 // created automatically when the authorization flow completes for the first
40 // time.
41 tokFile := "token.json"
42 tok, err := tokenFromFile(tokFile)
43 if err != nil {
44 tok = getTokenFromWeb(config)
45 saveToken(tokFile, tok)
46 }
47 return config.Client(context.Background(), tok)
48}
49
50// Request a token from the web, then returns the retrieved token.
51func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
52 authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
53 fmt.Printf("Go to the following link in your browser then type the "+
54 "authorization code: \n%v\n", authURL)
55
56 var authCode string
57 if _, err := fmt.Scan(&authCode); err != nil {
58 log.Fatalf("Unable to read authorization code %v", err)
59 }
60
61 tok, err := config.Exchange(context.TODO(), authCode)
62 if err != nil {
63 log.Fatalf("Unable to retrieve token from web %v", err)
64 }
65 return tok
66}
67
68// Retrieves a token from a local file.
69func tokenFromFile(file string) (*oauth2.Token, error) {
70 f, err := os.Open(file)
71 if err != nil {
72 return nil, err
73 }
74 defer f.Close()
75 tok := &oauth2.Token{}
76 err = json.NewDecoder(f).Decode(tok)
77 return tok, err
78}
79
80// Saves a token to a file path.
81func saveToken(path string, token *oauth2.Token) {
82 fmt.Printf("Saving credential file to: %s\n", path)
83 f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
84 if err != nil {
85 log.Fatalf("Unable to cache oauth token: %v", err)
86 }
87 defer f.Close()
88 json.NewEncoder(f).Encode(token)
89}
90
91func initGoogleAPI(spreadsheetId string, spreadsheetRange string) *GoogleAPI {
92 ctx := context.Background()
93 b, err := os.ReadFile("credentials.json")
94 if err != nil {
95 log.Fatalf("Unable to read client secret file: %v", err)
96 }
97
98 // If modifying these scopes, delete your previously saved token.json.
99 config, err := google.ConfigFromJSON(b, drive.DriveReadonlyScope, sheets.SpreadsheetsReadonlyScope)
100 if err != nil {
101 log.Fatalf("Unable to parse client secret file to config: %v", err)
102 }
103 client := getClient(config)
104 return &GoogleAPI{ctx, client, spreadsheetId, spreadsheetRange}
105}
106
107func getEntries(googleApi *GoogleAPI) ([]Entry, error) {
108 srv, err := sheets.NewService(googleApi.Ctx, option.WithHTTPClient(googleApi.Client))
109 if err != nil {
110 log.Fatalf("Unable to retrieve Sheets client: %v", err)
111 return nil, err
112 }
113
114 resp, err := srv.Spreadsheets.Values.Get(googleApi.spreadsheetId, googleApi.spreadsheetRange).Do()
115 if err != nil {
116 log.Fatalf("Unable to retrieve data from sheet: %v", err)
117 return nil, err
118 }
119
120 values := resp.Values
121 rows := make([]Entry, len(values))
122 for i, row := range values {
123 dateString := row[0].(string)
124 date, err := time.Parse("02/01/2006 15.04.05", dateString)
125 if err != nil {
126 log.Println("Error while parsing the following time and date string:", dateString)
127 date = time.Now()
128 }
129 rows[i] = Entry{row[3].(string)[33:], date.Format("2006-01"), row[1].(string), ""}
130 }
131 return rows, nil
132}
133
134func getFile(googleApi *GoogleAPI, fileID string, cachePath string) (string, error) {
135 srv, err := drive.NewService(googleApi.Ctx, option.WithHTTPClient(googleApi.Client))
136 if err != nil {
137 log.Fatalf("Unable to retrieve Drive client: %v", err)
138 }
139
140 fileInfo, err := srv.Files.Get(fileID).Fields("name").Do()
141 if err != nil {
142 return "", err
143 }
144 fileExt := filepath.Ext(fileInfo.Name)
145
146 fileName := fileID + fileExt
147 filePath := filepath.Join(cachePath, fileName)
148 out, err := os.Create(filePath)
149 if err != nil {
150 return "", err
151 }
152 defer out.Close()
153
154 res, err := srv.Files.Get(fileID).Download()
155 if err != nil {
156 return "", err
157 }
158 defer res.Body.Close()
159
160 _, err = io.Copy(out, res.Body)
161 if err != nil {
162 return "", err
163 }
164
165 return fileExt, nil
166}