Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.tpl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
JWT_SIGNING_KEY=testing
DB_CONNECTION=sqlite:///path/to/db.db
21 changes: 13 additions & 8 deletions cli/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/http"

"github.com/src-d/code-annotation/server"
"github.com/src-d/code-annotation/server/repository"
"github.com/src-d/code-annotation/server/dbutil"
"github.com/src-d/code-annotation/server/service"

"github.com/kelseyhightower/envconfig"
Expand All @@ -15,15 +15,23 @@ type appConfig struct {
Host string `envconfig:"HOST"`
Port int `envconfig:"PORT" default:"8080"`
UIDomain string `envconfig:"UI_DOMAIN" default:"http://127.0.0.1:8080"`
DBConn string `envconfig:"DB_CONNECTION" default:"sqlite://./internal.db"`
}

func main() {
// main configuration
var conf appConfig
envconfig.MustProcess("", &conf)

// create repos
userRepo := &repository.Users{}
// loger
logger := service.NewLogger()

// database
db, err := dbutil.Open(conf.DBConn, true)
if err != nil {
logger.Fatal(err)
}
defer db.Close()

// create services
var oauthConfig service.OAuthConfig
Expand All @@ -34,12 +42,9 @@ func main() {
envconfig.MustProcess("jwt", &jwtConfig)
jwt := service.NewJWT(jwtConfig.SigningKey)

// loger
logger := service.NewLogger()

// start the router
router := server.Router(logger, jwt, oauth, conf.UIDomain, userRepo, "build")
router := server.Router(logger, jwt, oauth, conf.UIDomain, db.SQLDB(), "build")
logger.Info("running...")
err := http.ListenAndServe(fmt.Sprintf("%s:%d", conf.Host, conf.Port), router)
err = http.ListenAndServe(fmt.Sprintf("%s:%d", conf.Host, conf.Port), router)
logger.Fatal(err)
}
21 changes: 10 additions & 11 deletions server/dbutil/dbcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ const (
alterSequenceSQL = `ALTER SEQUENCE <TABLE>_id_seq RESTART WITH $1`
)

var tables = []string{"users", "experiments", "file_pairs", "assignments"}

// Copy dumps the contents of the origin DB into the destination DB. The
// destination DB should be bootstrapped, but empty
func Copy(originDB DB, destDB DB, opts Options) error {

logger := opts.getLogger()

tx, err := destDB.sqlDB.Begin()
tx, err := destDB.Begin()
if err != nil {
return err
}
Expand All @@ -36,16 +38,15 @@ func Copy(originDB DB, destDB DB, opts Options) error {
}
}()

tables := []string{"users", "experiments", "file_pairs", "assignments"}

for _, table := range tables {
// SELECT * FROM <TABLE>
selectCmd := strings.Replace(dumpAllSQL, tablePlaceholder, table, 1)

rows, err := originDB.sqlDB.Query(selectCmd)
rows, err := originDB.Query(selectCmd)
if err != nil {
return err
}
defer rows.Close()

columnNames, _ := rows.Columns()
nColumns := len(columnNames)
Expand Down Expand Up @@ -102,9 +103,9 @@ func insertCmd(table string, n int) string {
return cmd + "(" + strings.Join(nArgs, ",") + ")"
}

// genericVals returns a slice of interface{}, each one a pointer to a string
// genericVals returns a slice of interface{}, each one a pointer to a NullString
func genericVals(nColumns int) []interface{} {
columnVals := make([]string, nColumns)
columnVals := make([]sql.NullString, nColumns)
columnValsPtr := make([]interface{}, nColumns)

for i := range columnVals {
Expand All @@ -122,13 +123,11 @@ func fixSequences(db DB, logger *log.Logger) {
return
}

tables := []string{"users", "experiments", "file_pairs"}

for _, table := range tables {
selectCmd := strings.Replace(maxIDSQL, tablePlaceholder, table, 1)

var maxID sql.NullInt64
err := db.sqlDB.QueryRow(selectCmd).Scan(&maxID)
err := db.QueryRow(selectCmd).Scan(&maxID)

if err != nil {
// With 0 rows there is no MAX(id)
Expand All @@ -141,10 +140,10 @@ func fixSequences(db DB, logger *log.Logger) {
newMax := fmt.Sprintf("%v", maxID.Int64+1)

// for some reason Exec() fails to substitute $1 with the argument
//_, err := db.sqlDB.Exec(alterCmd, newMax)
//_, err := db.Exec(alterCmd, newMax)

alterCmd = strings.Replace(alterCmd, "$1", newMax, 1)
_, err := db.sqlDB.Exec(alterCmd)
_, err := db.Exec(alterCmd)

if err != nil {
logger.Printf("Error while executing %q:\n%v\n", alterCmd, err)
Expand Down
26 changes: 14 additions & 12 deletions server/dbutil/dbutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ const (

// DB groups a sql.DB and the driver used to initialize it
type DB struct {
sqlDB *sql.DB
*sql.DB
driver driver
}

// Close closes the database, releasing any open resources.
func (db *DB) Close() error {
return db.sqlDB.Close()
func (db *DB) SQLDB() *sql.DB {
return db.DB
}

const (
Expand All @@ -41,7 +40,7 @@ const (
posgresIncrementType = "SERIAL"

createUsers = `CREATE TABLE IF NOT EXISTS users (
id <INCREMENT_TYPE>, github_username TEXT, auth TEXT, role INTEGER,
id <INCREMENT_TYPE>, login TEXT UNIQUE, username TEXT, avatar_url TEXT, role TEXT,
PRIMARY KEY (id))`
createExperiments = `CREATE TABLE IF NOT EXISTS experiments (
id <INCREMENT_TYPE>, name TEXT UNIQUE, description TEXT,
Expand All @@ -55,9 +54,11 @@ const (
PRIMARY KEY (id),
FOREIGN KEY(experiment_id) REFERENCES experiments(id))`
createAssignments = `CREATE TABLE IF NOT EXISTS assignments (
id <INCREMENT_TYPE>,
user_id INTEGER, pair_id INTEGER, experiment_id INTEGER,
answer INTEGER, duration INTEGER,
PRIMARY KEY (user_id, pair_id),
answer TEXT, duration INTEGER,
PRIMARY KEY (id),
UNIQUE (user_id, pair_id, experiment_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (pair_id) REFERENCES file_pairs(id),
FOREIGN KEY (experiment_id) REFERENCES experiments(id))`
Expand Down Expand Up @@ -146,7 +147,7 @@ func Bootstrap(db DB) error {
for _, table := range tables {
cmd := strings.Replace(table, incrementTypePlaceholder, colType, 1)

if _, err := db.sqlDB.Exec(cmd); err != nil {
if _, err := db.Exec(cmd); err != nil {
return err
}
}
Expand All @@ -157,9 +158,9 @@ func Bootstrap(db DB) error {
// Initialize populates the DB with default values. It is safe to call on a
// DB that is already initialized
func Initialize(db DB) error {
_, err := db.sqlDB.Exec(insertExperiments, defaultExperimentID)
_, err := db.Exec(insertExperiments, defaultExperimentID)
if db.driver == postgres && err == nil {
db.sqlDB.Exec(alterExperimentsSequence)
db.Exec(alterExperimentsSequence)
}

// Errors are ignored to allow initialization over an existing DB
Expand Down Expand Up @@ -187,12 +188,13 @@ func ImportFiles(originDB DB, destDB DB, opts Options) (success, failures int64,

logger := opts.getLogger()

rows, err := originDB.sqlDB.Query(selectFiles)
rows, err := originDB.Query(selectFiles)
if err != nil {
return 0, 0, err
}
defer rows.Close()

tx, err := destDB.sqlDB.Begin()
tx, err := destDB.Begin()
if err != nil {
return 0, 0, err
}
Expand Down
60 changes: 37 additions & 23 deletions server/handler/assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,33 @@ package handler

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"

"github.com/src-d/code-annotation/server/repository"
"github.com/src-d/code-annotation/server/serializer"
"github.com/src-d/code-annotation/server/service"

"github.com/go-chi/chi"
)

// GetAssignmentsForUserExperiment returns a function that returns a *serializer.Response
// with the assignments for the logged user and a passed experiment
// if these assignments do not already exist, they are created in advance
func GetAssignmentsForUserExperiment() RequestProcessFunc {
func GetAssignmentsForUserExperiment(repo *repository.Assignments) RequestProcessFunc {
return func(r *http.Request) (*serializer.Response, error) {
requestedExperimentID := chi.URLParam(r, "experimentId")
experimentID, err := strconv.Atoi(requestedExperimentID)
experimentID, err := urlParamInt(r, "experimentId")
if err != nil {
return nil, err
}

userID, err := service.GetUserID(r.Context())
if err != nil {
return nil, serializer.NewHTTPError(
http.StatusBadRequest, fmt.Sprintf("wrong format in experiment ID sent; received %s", requestedExperimentID),
)
return nil, err
}

userID := service.GetUserID(r.Context())
assignments, err := repository.GetAssignmentsFor(userID, experimentID)
assignments, err := repo.GetAll(userID, experimentID)
if err == repository.ErrNoAssignmentsInitialized {
if assignments, err = repository.CreateAssignmentsFor(userID, experimentID); err != nil {
return nil, fmt.Errorf("no available assignments")
if assignments, err = repo.Initialize(userID, experimentID); err != nil {
return nil, err
}
}

Expand All @@ -45,14 +42,30 @@ type assignmentRequest struct {
}

// SaveAssignment returns a function that saves the user answers as passed in the body request
func SaveAssignment() RequestProcessFunc {
func SaveAssignment(repo *repository.Assignments) RequestProcessFunc {
return func(r *http.Request) (*serializer.Response, error) {
requestedAssignmentID := chi.URLParam(r, "assignmentId")
assignmentID, err := strconv.Atoi(requestedAssignmentID)
assignmentID, err := urlParamInt(r, "assignmentId")
if err != nil {
return nil, serializer.NewHTTPError(
http.StatusBadRequest, fmt.Sprintf("wrong format in assignment ID sent; received %s", requestedAssignmentID),
)
return nil, err
}

assignment, err := repo.GetByID(assignmentID)
if err != nil {
return nil, err
}

if assignment == nil {
return nil, serializer.NewHTTPError(http.StatusNotFound, "assignment not found")
}

userID, err := service.GetUserID(r.Context())
if err != nil {
return nil, err
}

if userID != assignment.UserID {
return nil, serializer.NewHTTPError(http.StatusForbidden,
"logged in user is not the assignment's owner")
}

var assignmentRequest assignmentRequest
Expand All @@ -63,11 +76,12 @@ func SaveAssignment() RequestProcessFunc {
}

if err != nil {
return nil, fmt.Errorf("payload could not be read")
return nil, err
}

if err := repository.UpdateAssignment(assignmentID, assignmentRequest.Answer, assignmentRequest.Duration); err != nil {
return nil, fmt.Errorf("answer could not be saved")
err = repo.Update(assignmentID, assignmentRequest.Answer, assignmentRequest.Duration)
if err != nil {
return nil, err
}

return serializer.NewCountResponse(1), nil
Expand Down
24 changes: 20 additions & 4 deletions server/handler/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"

"github.com/src-d/code-annotation/server/model"
"github.com/src-d/code-annotation/server/repository"
"github.com/src-d/code-annotation/server/serializer"
"github.com/src-d/code-annotation/server/service"
Expand Down Expand Up @@ -34,21 +35,36 @@ func OAuthCallback(
}

code := r.FormValue("code")
user, err := oAuth.GetUser(r.Context(), code)
ghUser, err := oAuth.GetUser(r.Context(), code)
if err != nil {
logger.Errorf("oauth get user error: %s", err)
// FIXME can it be not server error? for wrong code
write(w, r, serializer.NewEmptyResponse(), err)
return
}

// FIXME with real repo we need to check does user exists already or not
if err := userRepo.Create(user); err != nil {
logger.Errorf("can't create user: %s", err)
user, err := userRepo.Get(ghUser.Login)
if err != nil {
logger.Error(err)
write(w, r, serializer.NewEmptyResponse(), err)
return
}

if user == nil {
user = &model.User{
Login: ghUser.Login,
Username: ghUser.Username,
AvatarURL: ghUser.AvatarURL,
Role: model.Requester}

err = userRepo.Create(user)
if err != nil {
logger.Errorf("can't create user: %s", err)
write(w, r, serializer.NewEmptyResponse(), err)
return
}
}

token, err := jwt.MakeToken(user)
if err != nil {
logger.Errorf("make jwt token error: %s", err)
Expand Down
19 changes: 8 additions & 11 deletions server/handler/experiments.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
package handler

import (
"fmt"
"net/http"
"strconv"

"github.com/src-d/code-annotation/server/repository"
"github.com/src-d/code-annotation/server/serializer"

"github.com/go-chi/chi"
)

// GetExperimentDetails returns a function that returns a *serializer.Response
// with the details of a requested experiment
func GetExperimentDetails() RequestProcessFunc {
func GetExperimentDetails(repo *repository.Experiments) RequestProcessFunc {
return func(r *http.Request) (*serializer.Response, error) {
requestedExperimentID := chi.URLParam(r, "experimentId")
experimentID, err := strconv.Atoi(requestedExperimentID)
experimentID, err := urlParamInt(r, "experimentId")
if err != nil {
return nil, serializer.NewHTTPError(
http.StatusBadRequest, fmt.Sprintf("wrong format in experiment ID sent; received %s", requestedExperimentID),
)
return nil, err
}

experiment, err := repository.GetExperimentByID(experimentID)
experiment, err := repo.GetByID(experimentID)
if err != nil {
return nil, err
}

if experiment == nil {
return nil, serializer.NewHTTPError(http.StatusNotFound, "no experiment found")
}

Expand Down
Loading