diff --git a/.env b/.env index f0f1fecc697b60d78a401477ee01c6c5febbc5a5..43bf5fd412feeda40517d5f5e722333b92271442 100644 --- a/.env +++ b/.env @@ -4,9 +4,6 @@ PORT=8080 LOGTAIL_TOKEN= HTTP_TIMEOUT_SEC=2 LOG_FLUSH_INTERVAL_MS=1000 -DB_STRING="host=localhost user=ocw password=ocw dbname=ocw-db port=5433 sslmode=disable TimeZone=Asia/Shanghai" SMTP_USERNAME="noreply@ocw.id" -SMTP_SERVER=localhost SMTP_PORT=1025 -REDIS_STRING="localhost" -FE_BASE_URL="http://localhost:3000" \ No newline at end of file +FE_BASE_URL="http://localhost:3000" diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000000000000000000000000000000000000..c251113ad3df4ab91c35cd7202eb9b5158ecc145 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,11 @@ +# Sesuaikan dengan environment masing-masing +BUCKET_ENDPOINT="minio:9000" +BUCKET_SECRET_KEY="SUTT" +BUCKET_ACCESS_ID="ACCESS" +BUKET_TOKEN_KEY="" +BUCKET_USE_SSL=false +BUCKET_NAME="ocw" + +SMTP_SERVER=localhost +DB_STRING="host=localhost user=ocw password=ocw dbname=ocw-db port=5433 sslmode=disable TimeZone=Asia/Shanghai" +REDIS_STRING="localhost" diff --git a/.gitignore b/.gitignore index c5c13586f4cb91a1d501686acc635db166cd0398..d3eeb070587bb63e0a455b491181f8fc408c8df1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ tmp/ wire_gen.go -__debug_bin* \ No newline at end of file +__debug_bin* +.env.local diff --git a/Makefile b/Makefile index 948871652a86d5e028ef4f13a7c2571766d27ec1..961e18808da80a896920915b35bbc96e27f321d0 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build: dependency @go build -o=bin/server.app . watch: dependency - @air --build.cmd="make build" --build.bin="./bin/server.app" --build.exclude_dir="bin,tmp,docs" --build.exclude_file="wire_gen.go" + @air --build.cmd="make build" --build.bin="./bin/server.app" --build.exclude_dir="bin,tmp,docs" --build.exclude_file="wire_gen.go" --build.kill_delay="0s" test: test-dependency @go test ./test/... -v diff --git a/model/domain/course/course.go b/model/domain/course/course.go new file mode 100644 index 0000000000000000000000000000000000000000..b80e0b4067f2d8b69b452949bedcf2a323992387 --- /dev/null +++ b/model/domain/course/course.go @@ -0,0 +1,19 @@ +package course + +import ( + "github.com/google/uuid" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/user" +) + +type Course struct { + Id string `gorm:"primaryKey"` + Name string + MajorId uuid.UUID + Description string + Major Major `gorm:"foreignKey:MajorId;references:Id"` + Contributors []user.User `gorm:"many2many:course_contributor;foreignKey:Id;joinForeignKey:CourseId;references:Email;joinReferences:Email"` +} + +func (Course) TableName() string { + return "course" +} diff --git a/model/domain/course/major.go b/model/domain/course/major.go new file mode 100644 index 0000000000000000000000000000000000000000..7b19ab02cc2421bd4e10d5be327c73009f96daac --- /dev/null +++ b/model/domain/course/major.go @@ -0,0 +1,12 @@ +package course + +import "github.com/google/uuid" + +type Major struct { + Id uuid.UUID `gorm:"type:uuid;primaryKey"` + Name string +} + +func (Major) TableName() string { + return "major" +} diff --git a/model/domain/material/content.go b/model/domain/material/content.go new file mode 100644 index 0000000000000000000000000000000000000000..0e181045ba1141a363e07b196bf985c9fc0f4adf --- /dev/null +++ b/model/domain/material/content.go @@ -0,0 +1,14 @@ +package material + +import "github.com/google/uuid" + +type Content struct { + Id uuid.UUID `gorm:"primaryKey"` + Type MaterialType + Link string + MaterialId uuid.UUID +} + +func (Content) TableName() string { + return "material_data" +} diff --git a/model/domain/material/material.go b/model/domain/material/material.go new file mode 100644 index 0000000000000000000000000000000000000000..55d6bfbf7d002af41a7edc6bdb50beed800650a0 --- /dev/null +++ b/model/domain/material/material.go @@ -0,0 +1,20 @@ +package material + +import ( + "github.com/google/uuid" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/course" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/user" +) + +type Material struct { + Id uuid.UUID `gorm:"primaryKey"` + CourseId string + CreatorEmail string + Creator user.User `gorm:"foreignKey:CreatorEmail;references:Email"` + Course course.Course `gorm:"foreignKey:CourseId;references:Id"` + Contents []Content `gorm:"foreignKey:MaterialId;references:Id"` +} + +func (Material) TableName() string { + return "material" +} diff --git a/model/domain/material/material_type.go b/model/domain/material/material_type.go new file mode 100644 index 0000000000000000000000000000000000000000..a4ed9ba30cf209e6a4655d05fa6e3ca3208e68ec --- /dev/null +++ b/model/domain/material/material_type.go @@ -0,0 +1,69 @@ +package material + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" +) + +type MaterialType int + +const ( + Video MaterialType = iota + Handout +) + +var roleMapping = map[MaterialType]string{ + Video: "video", + Handout: "handout", +} + +func (ur *MaterialType) Scan(value interface{}) error { + val := value.(string) + + for key, label := range roleMapping { + if label == val { + *ur = key + return nil + } + } + + return fmt.Errorf("invalid user role") +} + +func (u MaterialType) Value() (driver.Value, error) { + value, ok := roleMapping[u] + + if !ok { + return nil, fmt.Errorf("invalid user role") + } + + return value, nil +} + +func (u *MaterialType) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + for key, label := range roleMapping { + if label == s { + *u = key + return nil + } + } + + return fmt.Errorf("unkown role, given %s", s) +} + +func (u MaterialType) MarshalJSON() ([]byte, error) { + s, ok := roleMapping[u] + + if !ok { + return nil, errors.New("unkown user role") + } + + return json.Marshal(s) +} diff --git a/model/domain/quiz/options.go b/model/domain/quiz/options.go new file mode 100644 index 0000000000000000000000000000000000000000..de062c3cc531f34b4572016248e2085a2d1f1d99 --- /dev/null +++ b/model/domain/quiz/options.go @@ -0,0 +1,14 @@ +package quiz + +import "github.com/google/uuid" + +type AnswerOption struct { + Id uuid.UUID `gorm:"primaryKey"` + QuizProblemId uuid.UUID `gorm:"primaryKey"` + Statement string + IsAnswer bool +} + +func (AnswerOption) TableName() string { + return "quiz_choice_answer" +} diff --git a/model/domain/quiz/problem_type.go b/model/domain/quiz/problem_type.go new file mode 100644 index 0000000000000000000000000000000000000000..c03621526c635dc524f1caee1518665931b80d10 --- /dev/null +++ b/model/domain/quiz/problem_type.go @@ -0,0 +1,67 @@ +package quiz + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" +) + +type ProblemType int + +const ( + Choice ProblemType = iota +) + +var roleMapping = map[ProblemType]string{ + Choice: "choice", +} + +func (ur *ProblemType) Scan(value interface{}) error { + val := value.(string) + + for key, label := range roleMapping { + if label == val { + *ur = key + return nil + } + } + + return fmt.Errorf("invalid user role") +} + +func (u ProblemType) Value() (driver.Value, error) { + value, ok := roleMapping[u] + + if !ok { + return nil, fmt.Errorf("invalid user role") + } + + return value, nil +} + +func (u *ProblemType) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + for key, label := range roleMapping { + if label == s { + *u = key + return nil + } + } + + return fmt.Errorf("unkown role, given %s", s) +} + +func (u ProblemType) MarshalJSON() ([]byte, error) { + s, ok := roleMapping[u] + + if !ok { + return nil, errors.New("unkown user role") + } + + return json.Marshal(s) +} diff --git a/model/domain/quiz/quiz.go b/model/domain/quiz/quiz.go new file mode 100644 index 0000000000000000000000000000000000000000..6caade417ae857ec2a78fdf8c13b5ef007a8529a --- /dev/null +++ b/model/domain/quiz/quiz.go @@ -0,0 +1,21 @@ +package quiz + +import ( + "github.com/google/uuid" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/course" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/user" +) + +type Quiz struct { + Id uuid.UUID `gorm:"primaryKey"` + Name string + CourseId string + CreatorEmail string + Creator user.User `gorm:"foreignKey:CreatorEmail;references:Email"` + Course course.Course `gorm:"foreignKey:CourseId;references:Id"` + Problems []QuizProblem `gorm:"foreignKey:QuizId;references:Id"` +} + +func (Quiz) TableName() string { + return "quiz" +} diff --git a/model/domain/quiz/quiz_problem.go b/model/domain/quiz/quiz_problem.go new file mode 100644 index 0000000000000000000000000000000000000000..3ce4195465d2fd477567f18858296d949c60020c --- /dev/null +++ b/model/domain/quiz/quiz_problem.go @@ -0,0 +1,15 @@ +package quiz + +import "github.com/google/uuid" + +type QuizProblem struct { + Id uuid.UUID `gorm:"primaryKey"` + Statement string + Type ProblemType + QuizId uuid.UUID + Options []AnswerOption `gorm:"foreignKey:QuizProblemId;references:Id"` +} + +func (QuizProblem) TableName() string { + return "quiz_problem" +} diff --git a/model/domain/quiz/take.go b/model/domain/quiz/take.go new file mode 100644 index 0000000000000000000000000000000000000000..c66263f60f1e4009019e5dcd428e7c905784f243 --- /dev/null +++ b/model/domain/quiz/take.go @@ -0,0 +1,24 @@ +package quiz + +import ( + "os/user" + "time" + + "github.com/google/uuid" +) + +type QuizTake struct { + Id uuid.UUID `gorm:"primaryKey"` + QuizId uuid.UUID + Email string + StartTime time.Time + IsFinished bool + Score int + Quiz `gorm:"foreignKey:QuizId;references:Id"` + user.User `gorm:"foreignKey:Email;references:Email"` + ChoiceAnswers []TakeChoiceAnswer `gorm:"foreignKey:QuizTakeId;references:Id"` +} + +func (QuizTake) TableName() string { + return "quiz_take" +} diff --git a/model/domain/quiz/take_choice_answer.go b/model/domain/quiz/take_choice_answer.go new file mode 100644 index 0000000000000000000000000000000000000000..25e76746d8951251d3dbdee694986205ce364d53 --- /dev/null +++ b/model/domain/quiz/take_choice_answer.go @@ -0,0 +1,14 @@ +package quiz + +import "github.com/google/uuid" + +type TakeChoiceAnswer struct { + QuizTakeId uuid.UUID `gorm:"primaryKey"` + AnswerChoice uuid.UUID + QuizProblemId uuid.UUID `gorm:"primaryKey"` + AnswerOption `gorm:"foreignKey:AnswerChoice,QuizProblemId;references:Id,QuizProblemId"` +} + +func (TakeChoiceAnswer) TableName() string { + return "quiz_take_choice_answer" +} diff --git a/provider/storage/s3.go b/provider/storage/s3.go index 435b0bf2a2d44e985d3c5bf8d2be965035ea8921..3f746712f3fa34c1ae77ea87fe4491282e4a6609 100644 --- a/provider/storage/s3.go +++ b/provider/storage/s3.go @@ -15,8 +15,8 @@ func NewS3( env *env.Environment, ) (*S3Storage, error) { client, err := minio.New(env.BucketEndpoint, &minio.Options{ - Creds: credentials.NewStaticV4(env.BucketKeyId, env.BucketAccessKey, ""), - Secure: true, + Creds: credentials.NewStaticV4(env.BucketAccessKey, env.BucketSecretKey, env.BucketTokenKey), + Secure: env.BucketUseSSL, }) if err != nil { diff --git a/utils/env/env.go b/utils/env/env.go index 754b1ebb5ac87aa4e4649a6e7d6002196d37b55b..617338eb52e2950ad7720e131e3f60ad2e897f48 100644 --- a/utils/env/env.go +++ b/utils/env/env.go @@ -52,16 +52,17 @@ type Environment struct { RedisUseAuth bool `env:"REDIS_USE_AUTH" envDefault:"false"` RedisPrefixKey string `env:"REDIS_PREFIX_KEY" envDefault:"app:"` - BucketEndpoint string `env:"BUCKET_ENDPOINT"` - BucketAccessKey string `env:"BUCKET_ACCESS_KEY"` - BucketKeyId string `env:"BUCKET_KEY_ID"` - BucketUseSSL bool `env:"BUCKET_USE_SSL" envDefault:"true"` - BucketName string `env:"BUCKET_NAME"` + BucketEndpoint string `env:"BUCKET_ENDPOINT"` + BucketSecretKey string `env:"BUCKET_SECRET_KEY"` + BucketAccessKey string `env:"BUCKET_ACCESS_KEY"` + BucketTokenKey string `env:"BUCKET_TOKEN_KEY"` + BucketUseSSL bool `env:"BUCKET_USE_SSL" envDefault:"true"` + BucketName string `env:"BUCKET_NAME"` - BucketSignedPutDuration int64 `env:"BUCKET_SIGNED_PUT_DURATION_S" envDefault:"36000"` - BucketSignedGetDuration int64 `env:"BUCKET_SIGNED_GET_DURATION_S" envDefault:"1800"` + BucketSignedPutDuration int64 `env:"BUCKET_SIGNED_PUT_DURATION_S" envDefault:"36000"` + BucketSignedGetDuration int64 `env:"BUCKET_SIGNED_GET_DURATION_S" envDefault:"1800"` - BucketMaterialBasePath string `env:"BUCKET_MATERIAL_BASE_PATH" envDefault:"materials/"` + BucketMaterialBasePath string `env:"BUCKET_MATERIAL_BASE_PATH" envDefault:"materials/"` } func New() (*Environment, error) { @@ -83,7 +84,10 @@ func NewEnv() (*Environment, error) { } func NewDotEnv() (*Environment, error) { - err := godotenv.Load() + err := godotenv.Load( + ".env", + ".env.local", + ) if err != nil { return nil, err