all repos — artbound-go @ main

The official administration panel for ArtBound, by EarthBound Café.

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