Skip to content

Commit

Permalink
Merge pull request #82 from stamford-syntax-club/khing/add-term-sec
Browse files Browse the repository at this point in the history
  • Loading branch information
chinathaip authored Jun 30, 2024
2 parents 3547ee1 + cfe8769 commit 7d7ae49
Show file tree
Hide file tree
Showing 20 changed files with 367 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
run_install: false

- name: Install turbo
run: pnpm install --global turbo
run: pnpm install --global turbo@1.13.4

- name: Install jest
working-directory: ./apps/course-api
Expand Down
15 changes: 15 additions & 0 deletions apps/reviews-api/common/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"
"os"
"path/filepath"
"strconv"

"github.com/joho/godotenv"
)
Expand Down Expand Up @@ -47,3 +48,17 @@ func LoadEnvFile(appEnv string) error {
return nil
}

func GetBoolEnv(key string) bool {
val := os.Getenv(key)
if val == "" {
log.Printf("value of %s not set, defaulting to false", key)
return false
}

ret, err := strconv.ParseBool(val)
if err != nil {
log.Fatalf("error while parsing boolean value: %v", err)
}

return ret
}
4 changes: 3 additions & 1 deletion apps/reviews-api/docker-compose.integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ services:
POSTGRES_USER: syntax
POSTGRES_PASSWORD: stamford
volumes:
- ./review/data/datasource/db/init.sql:/docker-entrypoint-initdb.d/init.sql
- ./review/data/datasource/db/generated.sql:/docker-entrypoint-initdb.d/generated.sql
- ./review/data/datasource/db/seed.sql:/docker-entrypoint-initdb.d/seed.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
Expand Down Expand Up @@ -57,6 +58,7 @@ services:
- PRISMA_PROVIDER=go run github.com/steebchen/prisma-client-go
- PRISMA_OUTPUT=/review-app-integration/review/data/datasource/db
- JWT_SECRET=test-jwt-token
- ENABLE_TERM_SECTION_VALIDATION=true
depends_on:
- postgres-integration
- broker-integration
Expand Down
5 changes: 3 additions & 2 deletions apps/reviews-api/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "reviews-api",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"scripts": {
"dev": "go run main.go -environment=development.local",
"unit": "go clean -testcache && go test -tags=unit -v ./...",
"unit-coverage": "go clean -testcache && go test -tags=unit -coverprofile=coverage.out ./... && go tool cover -html=coverage.out -o coverage.html && open coverage.html",
"gen-sql": "go run github.com/steebchen/prisma-client-go migrate diff --from-empty --to-schema-datasource='../../packages/prisma/schema.prisma' --script > review/data/datasource/db/generated.sql",
"integration": "docker compose -f docker-compose.integration.yaml down -v && docker compose -f docker-compose.integration.yaml up --build --force-recreate --exit-code-from integration-test",
"bootstrap:unix": "PRISMA_PROVIDER='go run github.com/steebchen/prisma-client-go' PRISMA_OUTPUT='../../apps/reviews-api/review/data/datasource/db' go run github.com/steebchen/prisma-client-go generate --schema=../../packages/prisma/schema.prisma",
"bootstrap:windows": "@powershell $env:PRISMA_PROVIDER='go run github.com/steebchen/prisma-client-go'; $env:PRISMA_OUTPUT='../../apps/reviews-api/review/data/datasource/db'; go run github.com/steebchen/prisma-client-go generate --schema=../../packages/prisma/schema.prisma"
}
}
}
47 changes: 47 additions & 0 deletions apps/reviews-api/review/data/datasource/db/generated.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
-- CreateTable
CREATE TABLE "Course" (
"id" SERIAL NOT NULL,
"code" TEXT NOT NULL,
"full_name" TEXT NOT NULL,
"prerequisites" TEXT[],

CONSTRAINT "Course_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Profile" (
"id" UUID NOT NULL,
"username" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT false,

CONSTRAINT "Profile_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Review" (
"id" SERIAL NOT NULL,
"academic_year" INTEGER NOT NULL,
"description" TEXT NOT NULL,
"rating" DOUBLE PRECISION NOT NULL,
"votes" INTEGER NOT NULL,
"status" TEXT NOT NULL,
"rejectedReason" TEXT NOT NULL DEFAULT '',
"course_id" INTEGER NOT NULL,
"user_id" UUID NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3),
"section" INTEGER,
"term" INTEGER,

CONSTRAINT "Review_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Course_code_key" ON "Course"("code" ASC);

-- AddForeignKey
ALTER TABLE "Review" ADD CONSTRAINT "Review_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Review" ADD CONSTRAINT "Review_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "Profile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

48 changes: 0 additions & 48 deletions apps/reviews-api/review/data/datasource/db/init.sql

This file was deleted.

17 changes: 17 additions & 0 deletions apps/reviews-api/review/data/datasource/db/seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
INSERT INTO "Course" (code, full_name, prerequisites) VALUES
('CSCI101', 'Introduction to Computer Science', '{}'),
('MATH201', 'Calculus I', '{}'),
('PHYS101', 'Physics for Engineers', '{"MATH201"}'),
('NOREVIEW101', 'Course without reviews', '{}'),
('ITE221', 'Programming 1', '{"ITE103"}');

-- Inserting data into the Profile table
INSERT INTO "Profile" (id, "isActive", username) VALUES
('8a7b3c2e-3e5f-4f1a-a8b7-3c2e1a4f5b6d', true, NULL),
('2d1f3c4e-5a6b-7c8d-9e0f-1a2b3c4d5e6f', true, NULL),
('1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d', false, NULL),
('d5a59cb2-1f22-4e23-8ef0-7108e54f842b', true, NULL),
('6c7b1dd2-aa9d-4f5e-8a98-2c7c2895a95e', true, NULL),
('8b84c3b5-5b87-4c9b-832d-60d0966d4f7d', true, NULL),
('3f9e87a9-6d27-4a09-8a0a-20e58d609315', true, NULL),
('2b84c3b5-5b87-4c9b-832d-60d0966d4f7d', false, NULL);
17 changes: 17 additions & 0 deletions apps/reviews-api/review/data/repository/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ func hasExistingReview(ctx context.Context, prisma *db.PrismaClient, courseID in
return nil
}

func isTermValid(review *db.ReviewModel) error {
term, ok := review.Term()
if !ok || term < 0 || term > 3 {
return fiber.NewError(http.StatusBadRequest, "Invalid Term (must be between 1-3)")
}

return nil
}

func isSectionValid(review *db.ReviewModel) error {
if _, ok := review.Section(); !ok {
return fiber.NewError(http.StatusBadRequest, "Missing section")
}

return nil
}

func isReviewOwner(ctx context.Context, prisma *db.PrismaClient, submittedID, courseID int, userID string) error {
myReviewCh := make(chan *db.ReviewModel)

Expand Down
94 changes: 94 additions & 0 deletions apps/reviews-api/review/data/repository/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,100 @@ func TestGetMyReview(t *testing.T) {
})
}

func TestIsTermValid(t *testing.T) {

tests := []struct {
name string
term int
expectedError error
}{
{
name: "term 1 should not throw any errror",
term: 1,
},
{
name: "term 2 should not throw any errror",
term: 2,
},
{
name: "term 3 should not throw any errror",
term: 3,
},
{
name: "term 4 should throw invalid term",
term: 4,
expectedError: fiber.NewError(http.StatusBadRequest, "Invalid Term (must be between 1-3)"),
},
{
name: "term 0 should throw invalid term",
term: 0,
expectedError: fiber.NewError(http.StatusBadRequest, "Invalid Term (must be between 1-3)"),
},
{
name: "term -1 should throw invalid term",
term: -1,
expectedError: fiber.NewError(http.StatusBadRequest, "Invalid Term (must be between 1-3)"),
},
{
name: "empty term should throw invalid term",
expectedError: fiber.NewError(http.StatusBadRequest, "Invalid Term (must be between 1-3)"),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
review := &db.ReviewModel{
InnerReview: db.InnerReview{
Term: &test.term,
}}

err := isTermValid(review)

if test.expectedError != nil {
assert.Error(t, test.expectedError, err)
} else {
assert.Nil(t, err)
}
})
}
}

func TestIsSectionValid(t *testing.T) {
tests := []struct {
name string
section int
expectedError error
}{
{
name: "filled section should not throw any error",
section: 3,
},
{

name: "empty section should throw missing section",
expectedError: fiber.NewError(http.StatusBadRequest, "Missing section"),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
review := &db.ReviewModel{
InnerReview: db.InnerReview{
Section: &test.section,
},
}

err := isSectionValid(review)

if test.expectedError != nil {
assert.Error(t, test.expectedError, err)
} else {
assert.Nil(t, err)
}
})
}
}

func TestIsActiveUser(t *testing.T) {
client, mock, ensure := db.NewMock()
defer ensure(t)
Expand Down
31 changes: 31 additions & 0 deletions apps/reviews-api/review/data/repository/review_repo_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"log"

"github.com/gofiber/fiber/v2"

"github.com/stamford-syntax-club/course-compose/reviews/common/config"
"github.com/stamford-syntax-club/course-compose/reviews/common/utils"
"github.com/stamford-syntax-club/course-compose/reviews/review/data/datasource/db"
"github.com/stamford-syntax-club/course-compose/reviews/review/data/datasource/kafka"
Expand Down Expand Up @@ -61,6 +63,16 @@ func (rr *reviewRepositoryImpl) GetCourseReviews(ctx context.Context, courseCode
}

func (r *reviewRepositoryImpl) SubmitReview(ctx context.Context, review *db.ReviewModel, courseCode, userID string) (*db.ReviewModel, error) {
if config.GetBoolEnv("ENABLE_TERM_SECTION_VALIDATION") {
if err := isTermValid(review); err != nil {
return nil, err
}

if err := isSectionValid(review); err != nil {
return nil, err
}
}

courseID, err := getCourseID(ctx, r.reviewDB, courseCode)
if err != nil {
return nil, err
Expand All @@ -81,13 +93,20 @@ func (r *reviewRepositoryImpl) SubmitReview(ctx context.Context, review *db.Revi
db.Review.Votes.Set(0),
db.Review.Status.Set("PENDING"),
db.Review.Course.Link(db.Course.ID.Equals(courseID)),
db.Review.Term.SetIfPresent(review.InnerReview.Term),
db.Review.Section.SetIfPresent(review.InnerReview.Section),
db.Review.Profile.Link(db.Profile.ID.Equals(userID)),
).Exec(ctx)
if err != nil {
log.Println("exec create pending review: ", err)
return nil, fiber.ErrInternalServerError
}

if r.reviewKafka == nil {
log.Println("kafka producer is missing, no value will be produced to topic")
return result, nil
}

msg := dto.ReviewDTO{
ID: result.ID,
Rating: result.Rating,
Expand All @@ -107,6 +126,16 @@ func (r *reviewRepositoryImpl) SubmitReview(ctx context.Context, review *db.Revi
}

func (r *reviewRepositoryImpl) EditReview(ctx context.Context, review *db.ReviewModel, courseCode, userID string) (*db.ReviewModel, error) {
if config.GetBoolEnv("ENABLE_TERM_SECTION_VALIDATION") {
if err := isTermValid(review); err != nil {
return nil, err
}

if err := isSectionValid(review); err != nil {
return nil, err
}
}

courseID, err := getCourseID(ctx, r.reviewDB, courseCode)
if err != nil {
return nil, err
Expand All @@ -127,6 +156,8 @@ func (r *reviewRepositoryImpl) EditReview(ctx context.Context, review *db.Review
db.Review.Description.SetIfPresent(&review.Description),
db.Review.Rating.SetIfPresent(&review.Rating),
db.Review.Status.Set("PENDING"), // edited review must be evaluated again
db.Review.Term.SetIfPresent(review.InnerReview.Term),
db.Review.Section.SetIfPresent(review.InnerReview.Section),
).Exec(ctx)
if err != nil {
log.Println("exec updating review: ", err)
Expand Down
Loading

0 comments on commit 7d7ae49

Please sign in to comment.