Showing
11 changed files
with
440 additions
and
10 deletions
... | @@ -12,7 +12,8 @@ type Config struct { | ... | @@ -12,7 +12,8 @@ type Config struct { |
12 | } | 12 | } |
13 | 13 | ||
14 | type ConfigGoogle struct { | 14 | type ConfigGoogle struct { |
15 | - CredentialsPath string `json:"credentials_path"` | 15 | + CredentialsPath string `json:"credentials_path"` |
16 | + DriveRootFolderID string `json:"drive_root_folder_id"` | ||
16 | } | 17 | } |
17 | 18 | ||
18 | type ConfigServer struct { | 19 | type ConfigServer struct { | ... | ... |
... | @@ -12,7 +12,7 @@ import ( | ... | @@ -12,7 +12,7 @@ import ( |
12 | "github.com/julienschmidt/httprouter" | 12 | "github.com/julienschmidt/httprouter" |
13 | ) | 13 | ) |
14 | 14 | ||
15 | -// GET /files/<file_id>/<sheet_id>/cell | 15 | +// GET /files/<file_id>/sheets/<sheet_id>/cell |
16 | func (e *Endpoints) CellGet(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | 16 | func (e *Endpoints) CellGet(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
17 | // Get user email | 17 | // Get user email |
18 | var email string | 18 | var email string | ... | ... |
1 | package endpoints | 1 | package endpoints |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | + "bytes" | ||
4 | "classroom/functions" | 5 | "classroom/functions" |
5 | "classroom/models" | 6 | "classroom/models" |
6 | "database/sql" | 7 | "database/sql" |
8 | + "encoding/json" | ||
9 | + "io/ioutil" | ||
7 | "net/http" | 10 | "net/http" |
11 | + "os" | ||
12 | + "strings" | ||
8 | 13 | ||
9 | "github.com/julienschmidt/httprouter" | 14 | "github.com/julienschmidt/httprouter" |
10 | ) | 15 | ) |
... | @@ -76,3 +81,237 @@ func (e *Endpoints) FilesGet(w http.ResponseWriter, r *http.Request, ps httprout | ... | @@ -76,3 +81,237 @@ func (e *Endpoints) FilesGet(w http.ResponseWriter, r *http.Request, ps httprout |
76 | 81 | ||
77 | functions.ResponseOK(w, "success", resp) | 82 | functions.ResponseOK(w, "success", resp) |
78 | } | 83 | } |
84 | + | ||
85 | +// POST /files | ||
86 | +func (e *Endpoints) FilesPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | ||
87 | + // Get user email | ||
88 | + var email string | ||
89 | + if _email, ok := r.Header["X-User-Email"]; ok { | ||
90 | + email = _email[0] | ||
91 | + } else { | ||
92 | + functions.ResponseError(w, 401, "X-User-Email 헤더를 보내세요.") | ||
93 | + return | ||
94 | + } | ||
95 | + | ||
96 | + // Permission Check | ||
97 | + var isSuper int | ||
98 | + row := e.DB.QueryRow(` | ||
99 | + SELECT is_super FROM users WHERE email=?; | ||
100 | + `, email) | ||
101 | + if err := row.Scan(&isSuper); err != nil { | ||
102 | + if err == sql.ErrNoRows { | ||
103 | + functions.ResponseError(w, 401, "해당 유저가 존재하지 않습니다.") | ||
104 | + return | ||
105 | + } | ||
106 | + functions.ResponseError(w, 500, "예기치 못한 에러 : "+err.Error()) | ||
107 | + return | ||
108 | + } | ||
109 | + if isSuper == 0 { | ||
110 | + functions.ResponseError(w, 403, "접근 권한 부족. 관리자만 허용된 기능입니다.") | ||
111 | + return | ||
112 | + } | ||
113 | + | ||
114 | + // Parse multipart/form-data | ||
115 | + r.ParseMultipartForm(10 << 20) | ||
116 | + | ||
117 | + // Parsing File | ||
118 | + var fileName string | ||
119 | + file, handler, err := r.FormFile("file") | ||
120 | + if err != nil { | ||
121 | + functions.ResponseError(w, 500, "파일 로드 중 예기치 못한 에러 : "+err.Error()) | ||
122 | + return | ||
123 | + } | ||
124 | + defer file.Close() | ||
125 | + fileName = handler.Filename | ||
126 | + | ||
127 | + // Create temp file | ||
128 | + tempFile, err := ioutil.TempFile(os.TempDir(), "upload-*.tmp") | ||
129 | + if err != nil { | ||
130 | + functions.ResponseError(w, 500, "임시 파일 생성 중 예기치 못한 에러 : "+err.Error()) | ||
131 | + return | ||
132 | + } | ||
133 | + defer tempFile.Close() | ||
134 | + | ||
135 | + // Write data to temp file | ||
136 | + fileBytes, err := ioutil.ReadAll(file) | ||
137 | + if err != nil { | ||
138 | + functions.ResponseError(w, 500, "임시 파일 작성 중 예기치 못한 에러 : "+err.Error()) | ||
139 | + return | ||
140 | + } | ||
141 | + tempFile.Write(fileBytes) | ||
142 | + | ||
143 | + // [Drive] Upload file to Google Drive | ||
144 | + reader := bytes.NewReader(fileBytes) | ||
145 | + sheetFile, err := e.Drive.UploadFile("KHU Classroom Reservation", fileName, reader) | ||
146 | + if err != nil { | ||
147 | + functions.ResponseError(w, 500, "구글 드라이브에 파일 업로드 중 예기치 못한 에러 : "+err.Error()) | ||
148 | + return | ||
149 | + } | ||
150 | + | ||
151 | + // [Sheets] Get sheet properties of new file | ||
152 | + props, err := e.Sheets.GetAllSheetProperties(sheetFile.Id) | ||
153 | + if err != nil { | ||
154 | + functions.ResponseError(w, 500, "파일 속성 불러오기 중 예기치 못한 에러 : "+err.Error()) | ||
155 | + return | ||
156 | + } | ||
157 | + | ||
158 | + // Querying with Transaction | ||
159 | + tx, err := e.DB.Begin() | ||
160 | + if err != nil { | ||
161 | + functions.ResponseError(w, 500, "트랜잭션 시작 중 예기치 못한 에러 : "+err.Error()) | ||
162 | + return | ||
163 | + } | ||
164 | + defer tx.Rollback() | ||
165 | + | ||
166 | + _, err = tx.Exec(` | ||
167 | + INSERT INTO files (id, name) | ||
168 | + VALUES (?, ?); | ||
169 | + `, sheetFile.Id, fileName) | ||
170 | + if err != nil { | ||
171 | + functions.ResponseError(w, 500, "예기치 못한 에러 : "+err.Error()) | ||
172 | + return | ||
173 | + } | ||
174 | + | ||
175 | + query := "INSERT INTO sheets (id, name, file_id) VALUES " | ||
176 | + vals := []interface{}{} | ||
177 | + for idx := range props { | ||
178 | + query += "(?, ?, ?)," | ||
179 | + vals = append(vals, props[idx].SheetId, props[idx].Title, sheetFile.Id) | ||
180 | + } | ||
181 | + query = query[0 : len(query)-1] | ||
182 | + | ||
183 | + stmt, _ := tx.Prepare(query) | ||
184 | + _, err = stmt.Exec(vals...) | ||
185 | + if err != nil { | ||
186 | + functions.ResponseError(w, 500, "예기치 못한 에러 : "+err.Error()) | ||
187 | + return | ||
188 | + } | ||
189 | + | ||
190 | + err = tx.Commit() | ||
191 | + if err != nil { | ||
192 | + functions.ResponseError(w, 500, "예기치 못한 에러 : "+err.Error()) | ||
193 | + return | ||
194 | + } | ||
195 | + | ||
196 | + resp := models.FilesPostResponse{ | ||
197 | + FileID: sheetFile.Id, | ||
198 | + } | ||
199 | + | ||
200 | + functions.ResponseOK(w, "success", resp) | ||
201 | +} | ||
202 | + | ||
203 | +// POST /files/<file_id>/share | ||
204 | +func (e *Endpoints) FilesSharePost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | ||
205 | + // Get user email | ||
206 | + var email string | ||
207 | + if _email, ok := r.Header["X-User-Email"]; ok { | ||
208 | + email = _email[0] | ||
209 | + } else { | ||
210 | + functions.ResponseError(w, 401, "X-User-Email 헤더를 보내세요.") | ||
211 | + return | ||
212 | + } | ||
213 | + | ||
214 | + // Get Path Parameters | ||
215 | + fileID := ps.ByName("file_id") | ||
216 | + | ||
217 | + // Permission Check | ||
218 | + var isSuper int | ||
219 | + row := e.DB.QueryRow(` | ||
220 | + SELECT is_super FROM users WHERE email=?; | ||
221 | + `, email) | ||
222 | + if err := row.Scan(&isSuper); err != nil { | ||
223 | + if err == sql.ErrNoRows { | ||
224 | + functions.ResponseError(w, 401, "해당 유저가 존재하지 않습니다.") | ||
225 | + return | ||
226 | + } | ||
227 | + functions.ResponseError(w, 500, "예기치 못한 에러 : "+err.Error()) | ||
228 | + return | ||
229 | + } | ||
230 | + if isSuper == 0 { | ||
231 | + functions.ResponseError(w, 403, "접근 권한 부족. 관리자만 허용된 기능입니다.") | ||
232 | + return | ||
233 | + } | ||
234 | + | ||
235 | + // Parse Request Data | ||
236 | + type reqDataStruct struct { | ||
237 | + UserEmails []string `json:"user_emails"` | ||
238 | + } | ||
239 | + var reqData reqDataStruct | ||
240 | + if strings.Contains(r.Header.Get("Content-Type"), "application/json") { | ||
241 | + body, err := ioutil.ReadAll(r.Body) | ||
242 | + if err != nil { | ||
243 | + functions.ResponseError(w, 500, err.Error()) | ||
244 | + } | ||
245 | + json.Unmarshal(body, &reqData) | ||
246 | + } else { | ||
247 | + functions.ResponseError(w, 400, "JSON 형식만 가능합니다.") | ||
248 | + return | ||
249 | + } | ||
250 | + if len(reqData.UserEmails) == 0 { | ||
251 | + functions.ResponseError(w, 400, "user_emails를 한 개 이상 보내주세요.") | ||
252 | + return | ||
253 | + } | ||
254 | + | ||
255 | + // [Drive] Sharing file to users | ||
256 | + err := e.Drive.ShareFile(fileID, reqData.UserEmails) | ||
257 | + if err != nil { | ||
258 | + functions.ResponseError(w, 500, "파일 권한 설정 중 예기치 못한 에러 : "+err.Error()) | ||
259 | + return | ||
260 | + } | ||
261 | + | ||
262 | + functions.ResponseOK(w, "success", nil) | ||
263 | +} | ||
264 | + | ||
265 | +// POST /files/<file_id>/protect | ||
266 | +func (e *Endpoints) FilesProtectPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | ||
267 | + // Get user email | ||
268 | + var email string | ||
269 | + if _email, ok := r.Header["X-User-Email"]; ok { | ||
270 | + email = _email[0] | ||
271 | + } else { | ||
272 | + functions.ResponseError(w, 401, "X-User-Email 헤더를 보내세요.") | ||
273 | + return | ||
274 | + } | ||
275 | + | ||
276 | + // Get Path Parameters | ||
277 | + fileID := ps.ByName("file_id") | ||
278 | + | ||
279 | + // Permission Check | ||
280 | + var isSuper int | ||
281 | + row := e.DB.QueryRow(` | ||
282 | + SELECT is_super FROM users WHERE email=?; | ||
283 | + `, email) | ||
284 | + if err := row.Scan(&isSuper); err != nil { | ||
285 | + if err == sql.ErrNoRows { | ||
286 | + functions.ResponseError(w, 401, "해당 유저가 존재하지 않습니다.") | ||
287 | + return | ||
288 | + } | ||
289 | + functions.ResponseError(w, 500, "예기치 못한 에러 : "+err.Error()) | ||
290 | + return | ||
291 | + } | ||
292 | + if isSuper == 0 { | ||
293 | + functions.ResponseError(w, 403, "접근 권한 부족. 관리자만 허용된 기능입니다.") | ||
294 | + return | ||
295 | + } | ||
296 | + | ||
297 | + // [Sheets] Get sheet properties of new file | ||
298 | + props, err := e.Sheets.GetAllSheetProperties(fileID) | ||
299 | + if err != nil { | ||
300 | + functions.ResponseError(w, 500, "파일 속성 불러오기 중 예기치 못한 에러 : "+err.Error()) | ||
301 | + return | ||
302 | + } | ||
303 | + | ||
304 | + var sheetIDs []int64 | ||
305 | + for idx := range props { | ||
306 | + sheetIDs = append(sheetIDs, props[idx].SheetId) | ||
307 | + } | ||
308 | + | ||
309 | + // [Sheets] Protect all sheets | ||
310 | + err = e.Sheets.ProtectAll(fileID, sheetIDs) | ||
311 | + if err != nil { | ||
312 | + functions.ResponseError(w, 500, "셀 보호 설정 중 예기치 못한 에러 : "+err.Error()) | ||
313 | + return | ||
314 | + } | ||
315 | + | ||
316 | + functions.ResponseOK(w, "success", nil) | ||
317 | +} | ... | ... |
... | @@ -15,7 +15,7 @@ import ( | ... | @@ -15,7 +15,7 @@ import ( |
15 | "github.com/julienschmidt/httprouter" | 15 | "github.com/julienschmidt/httprouter" |
16 | ) | 16 | ) |
17 | 17 | ||
18 | -// POST /files/<file_id>/<sheet_id>/reservation | 18 | +// POST /files/<file_id>/sheets/<sheet_id>/reservation |
19 | func (e *Endpoints) ReservationPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | 19 | func (e *Endpoints) ReservationPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
20 | // Get user email | 20 | // Get user email |
21 | var email string | 21 | var email string |
... | @@ -190,7 +190,7 @@ loopCheckingValidation: | ... | @@ -190,7 +190,7 @@ loopCheckingValidation: |
190 | functions.ResponseOK(w, "success", resp) | 190 | functions.ResponseOK(w, "success", resp) |
191 | } | 191 | } |
192 | 192 | ||
193 | -// DELETE /files/<file_id>/<sheet_id>/reservation/<reservation_id> | 193 | +// DELETE /files/<file_id>/sheets/<sheet_id>/reservation/<reservation_id> |
194 | func (e *Endpoints) ReservationDelete(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | 194 | func (e *Endpoints) ReservationDelete(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
195 | // Get user email | 195 | // Get user email |
196 | var email string | 196 | var email string | ... | ... |
... | @@ -59,7 +59,11 @@ func main() { | ... | @@ -59,7 +59,11 @@ func main() { |
59 | defer db.Close() | 59 | defer db.Close() |
60 | 60 | ||
61 | // Google API Setting | 61 | // Google API Setting |
62 | - sheets, err := utils.NewSheetsService(cfg.Google.CredentialsPath) | 62 | + drive, err := utils.NewDriveService(cfg.Google.CredentialsPath, cfg.Google.DriveRootFolderID) // save token with drive, sheets scope |
63 | + if err != nil { | ||
64 | + log.Fatal(err) | ||
65 | + } | ||
66 | + sheets, err := utils.NewSheetsService(cfg.Google.CredentialsPath) // just load token | ||
63 | if err != nil { | 67 | if err != nil { |
64 | log.Fatal(err) | 68 | log.Fatal(err) |
65 | } | 69 | } |
... | @@ -67,6 +71,7 @@ func main() { | ... | @@ -67,6 +71,7 @@ func main() { |
67 | ep := endpoints.Endpoints{ | 71 | ep := endpoints.Endpoints{ |
68 | DB: db, | 72 | DB: db, |
69 | Sheets: sheets, | 73 | Sheets: sheets, |
74 | + Drive: drive, | ||
70 | } | 75 | } |
71 | 76 | ||
72 | // Router Setting | 77 | // Router Setting |
... | @@ -75,9 +80,12 @@ func main() { | ... | @@ -75,9 +80,12 @@ func main() { |
75 | router.GET("/api/users", ep.UsersGet) | 80 | router.GET("/api/users", ep.UsersGet) |
76 | router.POST("/api/users", ep.UsersPost) | 81 | router.POST("/api/users", ep.UsersPost) |
77 | router.GET("/api/files", ep.FilesGet) | 82 | router.GET("/api/files", ep.FilesGet) |
78 | - router.GET("/api/files/:file_id/:sheet_id/cell", ep.CellGet) | 83 | + router.POST("/api/files", ep.FilesPost) |
79 | - router.POST("/api/files/:file_id/:sheet_id/reservation", ep.ReservationPost) | 84 | + router.POST("/api/files/:file_id/share", ep.FilesSharePost) |
80 | - router.DELETE("/api/files/:file_id/:sheet_id/reservation/:reservation_id", ep.ReservationDelete) | 85 | + router.POST("/api/files/:file_id/protect", ep.FilesProtectPost) |
86 | + router.GET("/api/files/:file_id/sheets/:sheet_id/cell", ep.CellGet) | ||
87 | + router.POST("/api/files/:file_id/sheets/:sheet_id/reservation", ep.ReservationPost) | ||
88 | + router.DELETE("/api/files/:file_id/sheets/:sheet_id/reservation/:reservation_id", ep.ReservationDelete) | ||
81 | 89 | ||
82 | // Local Mode | 90 | // Local Mode |
83 | portStr := strconv.Itoa(cfg.Server.Port) | 91 | portStr := strconv.Itoa(cfg.Server.Port) | ... | ... |
... | @@ -10,3 +10,7 @@ type FilesGetItem struct { | ... | @@ -10,3 +10,7 @@ type FilesGetItem struct { |
10 | FileName string `json:"file_name"` | 10 | FileName string `json:"file_name"` |
11 | CreatedAt string `json:"created_at"` | 11 | CreatedAt string `json:"created_at"` |
12 | } | 12 | } |
13 | + | ||
14 | +type FilesPostResponse struct { | ||
15 | + FileID string `json:"file_id"` | ||
16 | +} | ... | ... |
api/utils/drive.go
0 → 100644
1 | +package utils | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "fmt" | ||
6 | + "io" | ||
7 | + "io/ioutil" | ||
8 | + "log" | ||
9 | + | ||
10 | + "golang.org/x/oauth2/google" | ||
11 | + "google.golang.org/api/drive/v3" | ||
12 | + "google.golang.org/api/option" | ||
13 | +) | ||
14 | + | ||
15 | +// DriveService is a wrapper for Google Drive Service and its context. | ||
16 | +type DriveService struct { | ||
17 | + srv *drive.Service | ||
18 | + ctx context.Context | ||
19 | + driveRootFolderID string | ||
20 | +} | ||
21 | + | ||
22 | +// NewDriveService is a factory function which returns a new DriveService{}. | ||
23 | +func NewDriveService(credentialsPath, driveRootFolderID string) (*DriveService, error) { | ||
24 | + b, err := ioutil.ReadFile(credentialsPath) | ||
25 | + if err != nil { | ||
26 | + log.Fatalf("Unable to read client secret file: %v", err) | ||
27 | + } | ||
28 | + | ||
29 | + // If modifying these scopes, delete your previously saved token.json. | ||
30 | + config, err := google.ConfigFromJSON(b, | ||
31 | + "https://www.googleapis.com/auth/drive", | ||
32 | + "https://www.googleapis.com/auth/spreadsheets", | ||
33 | + ) | ||
34 | + if err != nil { | ||
35 | + log.Fatalf("Unable to parse client secret file to config: %v", err) | ||
36 | + } | ||
37 | + client := getClient(config) | ||
38 | + | ||
39 | + ctx := context.Background() | ||
40 | + driveService, err := drive.NewService(ctx, | ||
41 | + option.WithHTTPClient(client), | ||
42 | + option.WithScopes(drive.DriveScope), | ||
43 | + ) | ||
44 | + if err != nil { | ||
45 | + return nil, err | ||
46 | + } | ||
47 | + | ||
48 | + srv := &DriveService{ | ||
49 | + srv: driveService, | ||
50 | + ctx: ctx, | ||
51 | + driveRootFolderID: driveRootFolderID, | ||
52 | + } | ||
53 | + | ||
54 | + return srv, nil | ||
55 | +} | ||
56 | + | ||
57 | +// UploadFile makes a request that uploads file to specific folder. | ||
58 | +func (s *DriveService) UploadFile(folderName, fileName string, content io.Reader) (*drive.File, error) { | ||
59 | + file, err := s.createFile(fileName, s.driveRootFolderID, content) | ||
60 | + if err != nil { | ||
61 | + return nil, err | ||
62 | + } | ||
63 | + | ||
64 | + return file, nil | ||
65 | +} | ||
66 | + | ||
67 | +// ShareFile makes a request that shares the file for specific users. | ||
68 | +func (s *DriveService) ShareFile(fileID string, emails []string) error { | ||
69 | + for _, email := range emails { | ||
70 | + perm := &drive.Permission{ | ||
71 | + Type: "user", | ||
72 | + Role: "writer", | ||
73 | + EmailAddress: email, | ||
74 | + } | ||
75 | + resp, err := s.srv.Permissions.Create(fileID, perm).Do() | ||
76 | + if err != nil { | ||
77 | + fmt.Println(resp, err) | ||
78 | + continue | ||
79 | + } | ||
80 | + } | ||
81 | + | ||
82 | + return nil | ||
83 | +} | ||
84 | + | ||
85 | +// createFolder creates a folder and returns its object. | ||
86 | +func (s *DriveService) createFolder(name, parentID string) (*drive.File, error) { | ||
87 | + d := &drive.File{ | ||
88 | + Name: name, | ||
89 | + MimeType: "application/vnd.google-apps.folder", | ||
90 | + Parents: []string{parentID}, | ||
91 | + } | ||
92 | + | ||
93 | + file, err := s.srv.Files.Create(d).Do() | ||
94 | + if err != nil { | ||
95 | + return nil, err | ||
96 | + } | ||
97 | + | ||
98 | + return file, nil | ||
99 | +} | ||
100 | + | ||
101 | +// createFile creates a file and returns its object. | ||
102 | +func (s *DriveService) createFile(name, parentID string, content io.Reader) (*drive.File, error) { | ||
103 | + f := &drive.File{ | ||
104 | + Name: name, | ||
105 | + MimeType: "application/vnd.google-apps.spreadsheet", | ||
106 | + Parents: []string{parentID}, | ||
107 | + } | ||
108 | + | ||
109 | + file, err := s.srv.Files.Create(f).Media(content).Do() | ||
110 | + if err != nil { | ||
111 | + return nil, err | ||
112 | + } | ||
113 | + | ||
114 | + return file, nil | ||
115 | +} | ||
116 | + | ||
117 | +type UploadRequest struct { | ||
118 | + FolderName string | ||
119 | + FileName string | ||
120 | + MimeType string | ||
121 | + Content io.Reader | ||
122 | +} |
... | @@ -11,11 +11,13 @@ import ( | ... | @@ -11,11 +11,13 @@ import ( |
11 | "google.golang.org/api/sheets/v4" | 11 | "google.golang.org/api/sheets/v4" |
12 | ) | 12 | ) |
13 | 13 | ||
14 | +// SheetsService is a wrapper for Spread Sheets Service and its context. | ||
14 | type SheetsService struct { | 15 | type SheetsService struct { |
15 | srv *sheets.Service | 16 | srv *sheets.Service |
16 | ctx context.Context | 17 | ctx context.Context |
17 | } | 18 | } |
18 | 19 | ||
20 | +// NewSheetsService is a factory function which returns a new SheetsService{}. | ||
19 | func NewSheetsService(credentialsPath string) (*SheetsService, error) { | 21 | func NewSheetsService(credentialsPath string) (*SheetsService, error) { |
20 | b, err := ioutil.ReadFile(credentialsPath) | 22 | b, err := ioutil.ReadFile(credentialsPath) |
21 | if err != nil { | 23 | if err != nil { |
... | @@ -46,6 +48,7 @@ func NewSheetsService(credentialsPath string) (*SheetsService, error) { | ... | @@ -46,6 +48,7 @@ func NewSheetsService(credentialsPath string) (*SheetsService, error) { |
46 | return srv, nil | 48 | return srv, nil |
47 | } | 49 | } |
48 | 50 | ||
51 | +// WriteAndMerge makes requests that merge cells and write value into cells. | ||
49 | func (s *SheetsService) WriteAndMerge(sr SheetsRequest) error { | 52 | func (s *SheetsService) WriteAndMerge(sr SheetsRequest) error { |
50 | req := &sheets.Request{} | 53 | req := &sheets.Request{} |
51 | req.MergeCells = &sheets.MergeCellsRequest{ | 54 | req.MergeCells = &sheets.MergeCellsRequest{ |
... | @@ -73,6 +76,7 @@ func (s *SheetsService) WriteAndMerge(sr SheetsRequest) error { | ... | @@ -73,6 +76,7 @@ func (s *SheetsService) WriteAndMerge(sr SheetsRequest) error { |
73 | return nil | 76 | return nil |
74 | } | 77 | } |
75 | 78 | ||
79 | +// RemoveValue makes requests that clear and unmerge cells. | ||
76 | func (s *SheetsService) RemoveValue(sr SheetsRequest) error { | 80 | func (s *SheetsService) RemoveValue(sr SheetsRequest) error { |
77 | req := &sheets.Request{} | 81 | req := &sheets.Request{} |
78 | req.UnmergeCells = &sheets.UnmergeCellsRequest{ | 82 | req.UnmergeCells = &sheets.UnmergeCellsRequest{ |
... | @@ -109,6 +113,54 @@ func (s *SheetsService) RemoveValue(sr SheetsRequest) error { | ... | @@ -109,6 +113,54 @@ func (s *SheetsService) RemoveValue(sr SheetsRequest) error { |
109 | return nil | 113 | return nil |
110 | } | 114 | } |
111 | 115 | ||
116 | +func (s *SheetsService) GetAllSheetProperties(fileID string) ([]*sheets.SheetProperties, error) { | ||
117 | + var result []*sheets.SheetProperties | ||
118 | + | ||
119 | + req, err := s.srv.Spreadsheets.Get(fileID).Do() | ||
120 | + if err != nil { | ||
121 | + return nil, err | ||
122 | + } | ||
123 | + | ||
124 | + for idx := range req.Sheets { | ||
125 | + result = append(result, req.Sheets[idx].Properties) | ||
126 | + } | ||
127 | + | ||
128 | + return result, nil | ||
129 | +} | ||
130 | + | ||
131 | +func (s *SheetsService) ProtectAll(fileID string, sheetIDs []int64) error { | ||
132 | + reqs := []*sheets.Request{} | ||
133 | + | ||
134 | + for _, sheetID := range sheetIDs { | ||
135 | + req := &sheets.Request{} | ||
136 | + req.AddProtectedRange = &sheets.AddProtectedRangeRequest{ | ||
137 | + ProtectedRange: &sheets.ProtectedRange{ | ||
138 | + Range: &sheets.GridRange{ | ||
139 | + SheetId: sheetID, | ||
140 | + StartColumnIndex: 0, | ||
141 | + EndColumnIndex: 100, | ||
142 | + StartRowIndex: 0, | ||
143 | + EndRowIndex: 1000, | ||
144 | + }, | ||
145 | + }, | ||
146 | + } | ||
147 | + | ||
148 | + reqs = append(reqs, req) | ||
149 | + } | ||
150 | + | ||
151 | + rb := &sheets.BatchUpdateSpreadsheetRequest{ | ||
152 | + Requests: reqs, | ||
153 | + } | ||
154 | + | ||
155 | + _, err := s.srv.Spreadsheets.BatchUpdate(fileID, rb).Context(s.ctx).Do() | ||
156 | + if err != nil { | ||
157 | + return err | ||
158 | + } | ||
159 | + | ||
160 | + return nil | ||
161 | +} | ||
162 | + | ||
163 | +// SheetsRequest is a wrapper for request, especially WriteAndMerge() and RemoveValue() functions. | ||
112 | type SheetsRequest struct { | 164 | type SheetsRequest struct { |
113 | SpreadSheetID string | 165 | SpreadSheetID string |
114 | SheetName string | 166 | SheetName string |
... | @@ -118,6 +170,7 @@ type SheetsRequest struct { | ... | @@ -118,6 +170,7 @@ type SheetsRequest struct { |
118 | Value string | 170 | Value string |
119 | } | 171 | } |
120 | 172 | ||
173 | +// NewSheetsRequest is a factory function which returns a new SheetsRequest{}. | ||
121 | func NewSheetsRequest( | 174 | func NewSheetsRequest( |
122 | spreadSheetID, sheetName string, sheetID int64, column string, start, end int64, value string) SheetsRequest { | 175 | spreadSheetID, sheetName string, sheetID int64, column string, start, end int64, value string) SheetsRequest { |
123 | colIndex := A1ToInt(column) | 176 | colIndex := A1ToInt(column) | ... | ... |
-
Please register or login to post a comment