freckie

Update

...@@ -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
......
...@@ -8,4 +8,5 @@ import ( ...@@ -8,4 +8,5 @@ import (
8 type Endpoints struct { 8 type Endpoints struct {
9 DB *sql.DB 9 DB *sql.DB
10 Sheets *utils.SheetsService 10 Sheets *utils.SheetsService
11 + Drive *utils.DriveService
11 } 12 }
......
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
......
...@@ -6,4 +6,6 @@ require ( ...@@ -6,4 +6,6 @@ require (
6 classroom/models v0.0.0 6 classroom/models v0.0.0
7 ) 7 )
8 8
9 -replace classroom/models v0.0.0 => ../models 9 +replace (
10 + classroom/models v0.0.0 => ../models
11 +)
......
...@@ -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 +}
......
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 +}
1 -module classroom 1 +module classroom/utils
2 2
3 go 1.14 3 go 1.14
4 4
......
...@@ -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)
......