Toggle navigation
Toggle navigation
This project
Loading...
Sign in
김명현
/
Classroom-Reservation
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
freckie
2020-12-22 14:47:41 +0900
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
148b103e9def8df4d43eaf40debc10563ce2d832
148b103e
1 parent
0acf04c7
Update
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
440 additions
and
10 deletions
api/config.go
api/endpoints/cell.go
api/endpoints/endpoints.go
api/endpoints/files.go
api/endpoints/reservation.go
api/functions/go.mod
api/main.go
api/models/files.go
api/utils/drive.go
api/utils/go.mod
api/utils/sheets.go
api/config.go
View file @
148b103
...
...
@@ -12,7 +12,8 @@ type Config struct {
}
type
ConfigGoogle
struct
{
CredentialsPath
string
`json:"credentials_path"`
CredentialsPath
string
`json:"credentials_path"`
DriveRootFolderID
string
`json:"drive_root_folder_id"`
}
type
ConfigServer
struct
{
...
...
api/endpoints/cell.go
View file @
148b103
...
...
@@ -12,7 +12,7 @@ import (
"github.com/julienschmidt/httprouter"
)
// GET /files/<file_id>/<sheet_id>/cell
// GET /files/<file_id>/
sheets/
<sheet_id>/cell
func
(
e
*
Endpoints
)
CellGet
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
ps
httprouter
.
Params
)
{
// Get user email
var
email
string
...
...
api/endpoints/endpoints.go
View file @
148b103
...
...
@@ -8,4 +8,5 @@ import (
type
Endpoints
struct
{
DB
*
sql
.
DB
Sheets
*
utils
.
SheetsService
Drive
*
utils
.
DriveService
}
...
...
api/endpoints/files.go
View file @
148b103
package
endpoints
import
(
"bytes"
"classroom/functions"
"classroom/models"
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/julienschmidt/httprouter"
)
...
...
@@ -76,3 +81,237 @@ func (e *Endpoints) FilesGet(w http.ResponseWriter, r *http.Request, ps httprout
functions
.
ResponseOK
(
w
,
"success"
,
resp
)
}
// POST /files
func
(
e
*
Endpoints
)
FilesPost
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
ps
httprouter
.
Params
)
{
// Get user email
var
email
string
if
_email
,
ok
:=
r
.
Header
[
"X-User-Email"
];
ok
{
email
=
_email
[
0
]
}
else
{
functions
.
ResponseError
(
w
,
401
,
"X-User-Email 헤더를 보내세요."
)
return
}
// Permission Check
var
isSuper
int
row
:=
e
.
DB
.
QueryRow
(
`
SELECT is_super FROM users WHERE email=?;
`
,
email
)
if
err
:=
row
.
Scan
(
&
isSuper
);
err
!=
nil
{
if
err
==
sql
.
ErrNoRows
{
functions
.
ResponseError
(
w
,
401
,
"해당 유저가 존재하지 않습니다."
)
return
}
functions
.
ResponseError
(
w
,
500
,
"예기치 못한 에러 : "
+
err
.
Error
())
return
}
if
isSuper
==
0
{
functions
.
ResponseError
(
w
,
403
,
"접근 권한 부족. 관리자만 허용된 기능입니다."
)
return
}
// Parse multipart/form-data
r
.
ParseMultipartForm
(
10
<<
20
)
// Parsing File
var
fileName
string
file
,
handler
,
err
:=
r
.
FormFile
(
"file"
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"파일 로드 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
defer
file
.
Close
()
fileName
=
handler
.
Filename
// Create temp file
tempFile
,
err
:=
ioutil
.
TempFile
(
os
.
TempDir
(),
"upload-*.tmp"
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"임시 파일 생성 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
defer
tempFile
.
Close
()
// Write data to temp file
fileBytes
,
err
:=
ioutil
.
ReadAll
(
file
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"임시 파일 작성 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
tempFile
.
Write
(
fileBytes
)
// [Drive] Upload file to Google Drive
reader
:=
bytes
.
NewReader
(
fileBytes
)
sheetFile
,
err
:=
e
.
Drive
.
UploadFile
(
"KHU Classroom Reservation"
,
fileName
,
reader
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"구글 드라이브에 파일 업로드 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
// [Sheets] Get sheet properties of new file
props
,
err
:=
e
.
Sheets
.
GetAllSheetProperties
(
sheetFile
.
Id
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"파일 속성 불러오기 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
// Querying with Transaction
tx
,
err
:=
e
.
DB
.
Begin
()
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"트랜잭션 시작 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
defer
tx
.
Rollback
()
_
,
err
=
tx
.
Exec
(
`
INSERT INTO files (id, name)
VALUES (?, ?);
`
,
sheetFile
.
Id
,
fileName
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"예기치 못한 에러 : "
+
err
.
Error
())
return
}
query
:=
"INSERT INTO sheets (id, name, file_id) VALUES "
vals
:=
[]
interface
{}{}
for
idx
:=
range
props
{
query
+=
"(?, ?, ?),"
vals
=
append
(
vals
,
props
[
idx
]
.
SheetId
,
props
[
idx
]
.
Title
,
sheetFile
.
Id
)
}
query
=
query
[
0
:
len
(
query
)
-
1
]
stmt
,
_
:=
tx
.
Prepare
(
query
)
_
,
err
=
stmt
.
Exec
(
vals
...
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"예기치 못한 에러 : "
+
err
.
Error
())
return
}
err
=
tx
.
Commit
()
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"예기치 못한 에러 : "
+
err
.
Error
())
return
}
resp
:=
models
.
FilesPostResponse
{
FileID
:
sheetFile
.
Id
,
}
functions
.
ResponseOK
(
w
,
"success"
,
resp
)
}
// POST /files/<file_id>/share
func
(
e
*
Endpoints
)
FilesSharePost
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
ps
httprouter
.
Params
)
{
// Get user email
var
email
string
if
_email
,
ok
:=
r
.
Header
[
"X-User-Email"
];
ok
{
email
=
_email
[
0
]
}
else
{
functions
.
ResponseError
(
w
,
401
,
"X-User-Email 헤더를 보내세요."
)
return
}
// Get Path Parameters
fileID
:=
ps
.
ByName
(
"file_id"
)
// Permission Check
var
isSuper
int
row
:=
e
.
DB
.
QueryRow
(
`
SELECT is_super FROM users WHERE email=?;
`
,
email
)
if
err
:=
row
.
Scan
(
&
isSuper
);
err
!=
nil
{
if
err
==
sql
.
ErrNoRows
{
functions
.
ResponseError
(
w
,
401
,
"해당 유저가 존재하지 않습니다."
)
return
}
functions
.
ResponseError
(
w
,
500
,
"예기치 못한 에러 : "
+
err
.
Error
())
return
}
if
isSuper
==
0
{
functions
.
ResponseError
(
w
,
403
,
"접근 권한 부족. 관리자만 허용된 기능입니다."
)
return
}
// Parse Request Data
type
reqDataStruct
struct
{
UserEmails
[]
string
`json:"user_emails"`
}
var
reqData
reqDataStruct
if
strings
.
Contains
(
r
.
Header
.
Get
(
"Content-Type"
),
"application/json"
)
{
body
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
err
.
Error
())
}
json
.
Unmarshal
(
body
,
&
reqData
)
}
else
{
functions
.
ResponseError
(
w
,
400
,
"JSON 형식만 가능합니다."
)
return
}
if
len
(
reqData
.
UserEmails
)
==
0
{
functions
.
ResponseError
(
w
,
400
,
"user_emails를 한 개 이상 보내주세요."
)
return
}
// [Drive] Sharing file to users
err
:=
e
.
Drive
.
ShareFile
(
fileID
,
reqData
.
UserEmails
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"파일 권한 설정 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
functions
.
ResponseOK
(
w
,
"success"
,
nil
)
}
// POST /files/<file_id>/protect
func
(
e
*
Endpoints
)
FilesProtectPost
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
ps
httprouter
.
Params
)
{
// Get user email
var
email
string
if
_email
,
ok
:=
r
.
Header
[
"X-User-Email"
];
ok
{
email
=
_email
[
0
]
}
else
{
functions
.
ResponseError
(
w
,
401
,
"X-User-Email 헤더를 보내세요."
)
return
}
// Get Path Parameters
fileID
:=
ps
.
ByName
(
"file_id"
)
// Permission Check
var
isSuper
int
row
:=
e
.
DB
.
QueryRow
(
`
SELECT is_super FROM users WHERE email=?;
`
,
email
)
if
err
:=
row
.
Scan
(
&
isSuper
);
err
!=
nil
{
if
err
==
sql
.
ErrNoRows
{
functions
.
ResponseError
(
w
,
401
,
"해당 유저가 존재하지 않습니다."
)
return
}
functions
.
ResponseError
(
w
,
500
,
"예기치 못한 에러 : "
+
err
.
Error
())
return
}
if
isSuper
==
0
{
functions
.
ResponseError
(
w
,
403
,
"접근 권한 부족. 관리자만 허용된 기능입니다."
)
return
}
// [Sheets] Get sheet properties of new file
props
,
err
:=
e
.
Sheets
.
GetAllSheetProperties
(
fileID
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"파일 속성 불러오기 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
var
sheetIDs
[]
int64
for
idx
:=
range
props
{
sheetIDs
=
append
(
sheetIDs
,
props
[
idx
]
.
SheetId
)
}
// [Sheets] Protect all sheets
err
=
e
.
Sheets
.
ProtectAll
(
fileID
,
sheetIDs
)
if
err
!=
nil
{
functions
.
ResponseError
(
w
,
500
,
"셀 보호 설정 중 예기치 못한 에러 : "
+
err
.
Error
())
return
}
functions
.
ResponseOK
(
w
,
"success"
,
nil
)
}
...
...
api/endpoints/reservation.go
View file @
148b103
...
...
@@ -15,7 +15,7 @@ import (
"github.com/julienschmidt/httprouter"
)
// POST /files/<file_id>/<sheet_id>/reservation
// POST /files/<file_id>/
sheets/
<sheet_id>/reservation
func
(
e
*
Endpoints
)
ReservationPost
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
ps
httprouter
.
Params
)
{
// Get user email
var
email
string
...
...
@@ -190,7 +190,7 @@ loopCheckingValidation:
functions
.
ResponseOK
(
w
,
"success"
,
resp
)
}
// DELETE /files/<file_id>/<sheet_id>/reservation/<reservation_id>
// DELETE /files/<file_id>/
sheets/
<sheet_id>/reservation/<reservation_id>
func
(
e
*
Endpoints
)
ReservationDelete
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
ps
httprouter
.
Params
)
{
// Get user email
var
email
string
...
...
api/functions/go.mod
View file @
148b103
...
...
@@ -6,4 +6,6 @@ require (
classroom/models v0.0.0
)
replace classroom/models v0.0.0 => ../models
replace (
classroom/models v0.0.0 => ../models
)
...
...
api/main.go
View file @
148b103
...
...
@@ -59,7 +59,11 @@ func main() {
defer
db
.
Close
()
// Google API Setting
sheets
,
err
:=
utils
.
NewSheetsService
(
cfg
.
Google
.
CredentialsPath
)
drive
,
err
:=
utils
.
NewDriveService
(
cfg
.
Google
.
CredentialsPath
,
cfg
.
Google
.
DriveRootFolderID
)
// save token with drive, sheets scope
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
sheets
,
err
:=
utils
.
NewSheetsService
(
cfg
.
Google
.
CredentialsPath
)
// just load token
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
...
...
@@ -67,6 +71,7 @@ func main() {
ep
:=
endpoints
.
Endpoints
{
DB
:
db
,
Sheets
:
sheets
,
Drive
:
drive
,
}
// Router Setting
...
...
@@ -75,9 +80,12 @@ func main() {
router
.
GET
(
"/api/users"
,
ep
.
UsersGet
)
router
.
POST
(
"/api/users"
,
ep
.
UsersPost
)
router
.
GET
(
"/api/files"
,
ep
.
FilesGet
)
router
.
GET
(
"/api/files/:file_id/:sheet_id/cell"
,
ep
.
CellGet
)
router
.
POST
(
"/api/files/:file_id/:sheet_id/reservation"
,
ep
.
ReservationPost
)
router
.
DELETE
(
"/api/files/:file_id/:sheet_id/reservation/:reservation_id"
,
ep
.
ReservationDelete
)
router
.
POST
(
"/api/files"
,
ep
.
FilesPost
)
router
.
POST
(
"/api/files/:file_id/share"
,
ep
.
FilesSharePost
)
router
.
POST
(
"/api/files/:file_id/protect"
,
ep
.
FilesProtectPost
)
router
.
GET
(
"/api/files/:file_id/sheets/:sheet_id/cell"
,
ep
.
CellGet
)
router
.
POST
(
"/api/files/:file_id/sheets/:sheet_id/reservation"
,
ep
.
ReservationPost
)
router
.
DELETE
(
"/api/files/:file_id/sheets/:sheet_id/reservation/:reservation_id"
,
ep
.
ReservationDelete
)
// Local Mode
portStr
:=
strconv
.
Itoa
(
cfg
.
Server
.
Port
)
...
...
api/models/files.go
View file @
148b103
...
...
@@ -10,3 +10,7 @@ type FilesGetItem struct {
FileName
string
`json:"file_name"`
CreatedAt
string
`json:"created_at"`
}
type
FilesPostResponse
struct
{
FileID
string
`json:"file_id"`
}
...
...
api/utils/drive.go
0 → 100644
View file @
148b103
package
utils
import
(
"context"
"fmt"
"io"
"io/ioutil"
"log"
"golang.org/x/oauth2/google"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
)
// DriveService is a wrapper for Google Drive Service and its context.
type
DriveService
struct
{
srv
*
drive
.
Service
ctx
context
.
Context
driveRootFolderID
string
}
// NewDriveService is a factory function which returns a new DriveService{}.
func
NewDriveService
(
credentialsPath
,
driveRootFolderID
string
)
(
*
DriveService
,
error
)
{
b
,
err
:=
ioutil
.
ReadFile
(
credentialsPath
)
if
err
!=
nil
{
log
.
Fatalf
(
"Unable to read client secret file: %v"
,
err
)
}
// If modifying these scopes, delete your previously saved token.json.
config
,
err
:=
google
.
ConfigFromJSON
(
b
,
"https://www.googleapis.com/auth/drive"
,
"https://www.googleapis.com/auth/spreadsheets"
,
)
if
err
!=
nil
{
log
.
Fatalf
(
"Unable to parse client secret file to config: %v"
,
err
)
}
client
:=
getClient
(
config
)
ctx
:=
context
.
Background
()
driveService
,
err
:=
drive
.
NewService
(
ctx
,
option
.
WithHTTPClient
(
client
),
option
.
WithScopes
(
drive
.
DriveScope
),
)
if
err
!=
nil
{
return
nil
,
err
}
srv
:=
&
DriveService
{
srv
:
driveService
,
ctx
:
ctx
,
driveRootFolderID
:
driveRootFolderID
,
}
return
srv
,
nil
}
// UploadFile makes a request that uploads file to specific folder.
func
(
s
*
DriveService
)
UploadFile
(
folderName
,
fileName
string
,
content
io
.
Reader
)
(
*
drive
.
File
,
error
)
{
file
,
err
:=
s
.
createFile
(
fileName
,
s
.
driveRootFolderID
,
content
)
if
err
!=
nil
{
return
nil
,
err
}
return
file
,
nil
}
// ShareFile makes a request that shares the file for specific users.
func
(
s
*
DriveService
)
ShareFile
(
fileID
string
,
emails
[]
string
)
error
{
for
_
,
email
:=
range
emails
{
perm
:=
&
drive
.
Permission
{
Type
:
"user"
,
Role
:
"writer"
,
EmailAddress
:
email
,
}
resp
,
err
:=
s
.
srv
.
Permissions
.
Create
(
fileID
,
perm
)
.
Do
()
if
err
!=
nil
{
fmt
.
Println
(
resp
,
err
)
continue
}
}
return
nil
}
// createFolder creates a folder and returns its object.
func
(
s
*
DriveService
)
createFolder
(
name
,
parentID
string
)
(
*
drive
.
File
,
error
)
{
d
:=
&
drive
.
File
{
Name
:
name
,
MimeType
:
"application/vnd.google-apps.folder"
,
Parents
:
[]
string
{
parentID
},
}
file
,
err
:=
s
.
srv
.
Files
.
Create
(
d
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
return
file
,
nil
}
// createFile creates a file and returns its object.
func
(
s
*
DriveService
)
createFile
(
name
,
parentID
string
,
content
io
.
Reader
)
(
*
drive
.
File
,
error
)
{
f
:=
&
drive
.
File
{
Name
:
name
,
MimeType
:
"application/vnd.google-apps.spreadsheet"
,
Parents
:
[]
string
{
parentID
},
}
file
,
err
:=
s
.
srv
.
Files
.
Create
(
f
)
.
Media
(
content
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
return
file
,
nil
}
type
UploadRequest
struct
{
FolderName
string
FileName
string
MimeType
string
Content
io
.
Reader
}
api/utils/go.mod
View file @
148b103
module classroom
module classroom
/utils
go 1.14
...
...
api/utils/sheets.go
View file @
148b103
...
...
@@ -11,11 +11,13 @@ import (
"google.golang.org/api/sheets/v4"
)
// SheetsService is a wrapper for Spread Sheets Service and its context.
type
SheetsService
struct
{
srv
*
sheets
.
Service
ctx
context
.
Context
}
// NewSheetsService is a factory function which returns a new SheetsService{}.
func
NewSheetsService
(
credentialsPath
string
)
(
*
SheetsService
,
error
)
{
b
,
err
:=
ioutil
.
ReadFile
(
credentialsPath
)
if
err
!=
nil
{
...
...
@@ -46,6 +48,7 @@ func NewSheetsService(credentialsPath string) (*SheetsService, error) {
return
srv
,
nil
}
// WriteAndMerge makes requests that merge cells and write value into cells.
func
(
s
*
SheetsService
)
WriteAndMerge
(
sr
SheetsRequest
)
error
{
req
:=
&
sheets
.
Request
{}
req
.
MergeCells
=
&
sheets
.
MergeCellsRequest
{
...
...
@@ -73,6 +76,7 @@ func (s *SheetsService) WriteAndMerge(sr SheetsRequest) error {
return
nil
}
// RemoveValue makes requests that clear and unmerge cells.
func
(
s
*
SheetsService
)
RemoveValue
(
sr
SheetsRequest
)
error
{
req
:=
&
sheets
.
Request
{}
req
.
UnmergeCells
=
&
sheets
.
UnmergeCellsRequest
{
...
...
@@ -109,6 +113,54 @@ func (s *SheetsService) RemoveValue(sr SheetsRequest) error {
return
nil
}
func
(
s
*
SheetsService
)
GetAllSheetProperties
(
fileID
string
)
([]
*
sheets
.
SheetProperties
,
error
)
{
var
result
[]
*
sheets
.
SheetProperties
req
,
err
:=
s
.
srv
.
Spreadsheets
.
Get
(
fileID
)
.
Do
()
if
err
!=
nil
{
return
nil
,
err
}
for
idx
:=
range
req
.
Sheets
{
result
=
append
(
result
,
req
.
Sheets
[
idx
]
.
Properties
)
}
return
result
,
nil
}
func
(
s
*
SheetsService
)
ProtectAll
(
fileID
string
,
sheetIDs
[]
int64
)
error
{
reqs
:=
[]
*
sheets
.
Request
{}
for
_
,
sheetID
:=
range
sheetIDs
{
req
:=
&
sheets
.
Request
{}
req
.
AddProtectedRange
=
&
sheets
.
AddProtectedRangeRequest
{
ProtectedRange
:
&
sheets
.
ProtectedRange
{
Range
:
&
sheets
.
GridRange
{
SheetId
:
sheetID
,
StartColumnIndex
:
0
,
EndColumnIndex
:
100
,
StartRowIndex
:
0
,
EndRowIndex
:
1000
,
},
},
}
reqs
=
append
(
reqs
,
req
)
}
rb
:=
&
sheets
.
BatchUpdateSpreadsheetRequest
{
Requests
:
reqs
,
}
_
,
err
:=
s
.
srv
.
Spreadsheets
.
BatchUpdate
(
fileID
,
rb
)
.
Context
(
s
.
ctx
)
.
Do
()
if
err
!=
nil
{
return
err
}
return
nil
}
// SheetsRequest is a wrapper for request, especially WriteAndMerge() and RemoveValue() functions.
type
SheetsRequest
struct
{
SpreadSheetID
string
SheetName
string
...
...
@@ -118,6 +170,7 @@ type SheetsRequest struct {
Value
string
}
// NewSheetsRequest is a factory function which returns a new SheetsRequest{}.
func
NewSheetsRequest
(
spreadSheetID
,
sheetName
string
,
sheetID
int64
,
column
string
,
start
,
end
int64
,
value
string
)
SheetsRequest
{
colIndex
:=
A1ToInt
(
column
)
...
...
Please
register
or
login
to post a comment