Skip to content

Commit

Permalink
[Admin] Course search for course creation (TUM-Dev#700)
Browse files Browse the repository at this point in the history
* preload courses from tum online

* fix some styling

* accessibility improvements
  • Loading branch information
joschahenningsen authored Oct 29, 2022
1 parent 659d39a commit 39c4e37
Show file tree
Hide file tree
Showing 13 changed files with 328 additions and 47 deletions.
18 changes: 18 additions & 0 deletions api/courses.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func configGinCourseRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) {
lecturers.Use(tools.AtLeastLecturer)
lecturers.POST("/courseInfo", routes.courseInfo)
lecturers.POST("/createCourse", routes.createCourse)
lecturers.GET("/searchCourse", routes.searchCourse)
}

api.DELETE("/course/by-token/:courseID", routes.deleteCourseByToken)
Expand Down Expand Up @@ -1211,6 +1212,23 @@ func (r coursesRoutes) copyCourse(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"numErrs": numErrors, "newCourse": course.ID})
}

func (r coursesRoutes) searchCourse(c *gin.Context) {
var request struct {
Q string `form:"q"`
}
err := c.BindQuery(&request)
if err != nil {
_ = c.Error(tools.RequestError{Status: http.StatusBadRequest, CustomMessage: "Bad request", Err: err})
return
}
courses, err := r.PrefetchedCourseDao.Search(c, request.Q)
if err != nil {
_ = c.Error(tools.RequestError{Status: http.StatusInternalServerError, CustomMessage: "Can't search course", Err: err})
return
}
c.JSON(http.StatusOK, courses)
}

type getCourseRequest struct {
CourseID string `json:"courseID"`
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/tumlive/tumlive.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ func main() {
&model.InfoPage{},
&model.Bookmark{},
&model.TranscodingProgress{},
&model.PrefetchedCourse{},
)
if err != nil {
sentry.CaptureException(err)
Expand Down Expand Up @@ -199,6 +200,8 @@ func initCron() {
_, _ = cronService.AddFunc("0-59/5 * * * *", func() { sentry.Flush(time.Minute * 2) })
//Look for due streams and notify workers about them
_, _ = cronService.AddFunc("0-59 * * * *", api.NotifyWorkers(daoWrapper))
// update courses available every monday at 3am
_, _ = cronService.AddFunc("0 0 * * 3", tum.PrefetchCourses(daoWrapper))
cronService.Start()
}

Expand Down
7 changes: 7 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ auths:
smpuser: username
campus:
base: https://example.tum.de/api/v1/
campusProxy: # new services use this proxy from now on
host: campus-proxy.mm.rbg.tum.de
scheme: https
relevantOrgs: # 0 = all
- 14189 # institut für informatik
- 14178 # fakultät für mathematik
- 14179 # fakultät für physik
tokens:
- secret1
- secret2
Expand Down
4 changes: 3 additions & 1 deletion dao/dao_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ type DaoWrapper struct {
// AuditDao.Find(...) seems like a nice api, find can be used in other dao as well if type is not embedded
AuditDao AuditDao
InfoPageDao
BookmarkDao BookmarkDao
BookmarkDao BookmarkDao
PrefetchedCourseDao PrefetchedCourseDao
}

func NewDaoWrapper() DaoWrapper {
Expand All @@ -61,5 +62,6 @@ func NewDaoWrapper() DaoWrapper {
KeywordDao: NewKeywordDao(),
AuditDao: NewAuditDao(),
BookmarkDao: NewBookmarkDao(),
PrefetchedCourseDao: NewPrefetchedCourseDao(),
}
}
59 changes: 59 additions & 0 deletions dao/prefetchedCourse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dao

import (
"context"
"github.com/joschahenningsen/TUM-Live/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

//go:generate mockgen -source=prefetchedCourse.go -destination ../mock_dao/prefetchedCourse.go

type PrefetchedCourseDao interface {
// Get PrefetchedCourse by ID
Get(context.Context, uint) (model.PrefetchedCourse, error)

// Create a new PrefetchedCourse for the database
Create(context.Context, ...*model.PrefetchedCourse) error

// Delete a PrefetchedCourse by id.
Delete(context.Context, uint) error

// Search for a PrefetchedCourse by name using fulltext index
Search(context.Context, string) ([]model.PrefetchedCourse, error)
}

type prefetchedCourseDao struct {
db *gorm.DB
}

func NewPrefetchedCourseDao() PrefetchedCourseDao {
return prefetchedCourseDao{db: DB}
}

// Get a PrefetchedCourse by id.
func (d prefetchedCourseDao) Get(c context.Context, id uint) (res model.PrefetchedCourse, err error) {
return res, DB.WithContext(c).First(&res, id).Error
}

// Create a PrefetchedCourse.
func (d prefetchedCourseDao) Create(c context.Context, it ...*model.PrefetchedCourse) error {
return DB.WithContext(c).Clauses(clause.OnConflict{DoNothing: true}).Create(it).Error
}

// Delete a PrefetchedCourse by id.
func (d prefetchedCourseDao) Delete(c context.Context, id uint) error {
return DB.WithContext(c).Delete(&model.PrefetchedCourse{}, id).Error
}

// Search for a PrefetchedCourse by name using fulltext index
func (d prefetchedCourseDao) Search(ctx context.Context, s string) ([]model.PrefetchedCourse, error) {
var res = make([]model.PrefetchedCourse, 0)
err := DB.WithContext(ctx).
Raw("WITH tmp as (SELECT *, MATCH (name) AGAINST(? IN NATURAL LANGUAGE MODE) AS m "+
"FROM "+(&model.PrefetchedCourse{}).TableName()+
") SELECT * FROM tmp WHERE m>0 ORDER BY m DESC LIMIT 10", s).
Scan(&res).
Error
return res, err
}
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,15 @@ require (
github.com/u2takey/go-utils v0.3.1
)

require github.com/matthiasreumann/gomino v0.0.2
require (
github.com/TUM-Dev/CampusProxy/client v0.0.0-20220928080722-4bd1259b2d06
github.com/matthiasreumann/gomino v0.0.2
)

require (
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
google.golang.org/appengine v1.6.7 // indirect
)

require (
// this version works - newer commits may have breaking changes
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ github.com/RBG-TUM/go-anel-pwrctrl v1.0.0 h1:8V7MV6oyg+sQzSRWhEfgg9FcOaQMGBreOh3
github.com/RBG-TUM/go-anel-pwrctrl v1.0.0/go.mod h1:6c2j1rcryE6BWphhJtGb+yXFIGRzmIXi+31TjE9Cft4=
github.com/RBG-TUM/go-extron v0.0.0-20210921110740-7a8f40430769 h1:CXcHbGcaclWMbveemm6tDADXDTcBo2OWiB89lutvY/U=
github.com/RBG-TUM/go-extron v0.0.0-20210921110740-7a8f40430769/go.mod h1:Gk99WHh7DoWzb964JwKjNGeDPe2Ulw7Vl7FP5y68wmw=
github.com/TUM-Dev/CampusProxy/client v0.0.0-20220928080722-4bd1259b2d06 h1:0tiyVB3cE4fy0sufENoYIz+NcWXF0PcfO06GLnHZ/98=
github.com/TUM-Dev/CampusProxy/client v0.0.0-20220928080722-4bd1259b2d06/go.mod h1:UuXuuqgljQbgtLAw5NY+VcThXAwO6UISn6U3s4TJx8k=
github.com/antchfx/xmlquery v1.3.12 h1:6TMGpdjpO/P8VhjnaYPXuqT3qyJ/VsqoyNTmJzNBTQ4=
github.com/antchfx/xmlquery v1.3.12/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ=
github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8=
Expand Down Expand Up @@ -465,6 +467,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -615,6 +619,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
Expand Down
79 changes: 79 additions & 0 deletions mock_dao/prefetchedCourse.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions model/prefetchedCourse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package model

// PrefetchedCourse represents a course we found in tumonline. This course can be found and created through the "create course" user interface
type PrefetchedCourse struct {
CourseID string `gorm:"column:course_id;type:varchar(255);primaryKey"`
Name string `gorm:"column:name;type:varchar(512);index:,class:FULLTEXT;not null;"`
Year int `gorm:"column:year;not null;"`
Term string `gorm:"column:term;type:varchar(1);not null;"` // Either W or S
}

// TableName returns the name of the table for the PrefetchedCourse model in the database.
func (*PrefetchedCourse) TableName() string {
return "prefetched_courses"
}
9 changes: 7 additions & 2 deletions tools/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,13 @@ type Config struct {
Port uint `yaml:"port"`
} `yaml:"db"`
Campus struct {
Base string `yaml:"base"`
Tokens []string `yaml:"tokens"`
Base string `yaml:"base"`
Tokens []string `yaml:"tokens"`
CampusProxy *struct {
Host string `yaml:"host"`
Scheme string `yaml:"scheme"`
} `yaml:"campusProxy"`
RelevantOrgs *[]string `yaml:"relevantOrgs"`
} `yaml:"campus"`
Ldap struct {
URL string `yaml:"url"`
Expand Down
71 changes: 71 additions & 0 deletions tools/tum/courses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package tum

import (
"context"
"fmt"
"github.com/TUM-Dev/CampusProxy/client"
"github.com/joschahenningsen/TUM-Live/dao"
"github.com/joschahenningsen/TUM-Live/model"
"github.com/joschahenningsen/TUM-Live/tools"
log "github.com/sirupsen/logrus"
"strconv"
"strings"
)

// PrefetchCourses loads all courses from tumonline, so we can use them in the course creation from search
func PrefetchCourses(dao dao.DaoWrapper) func() {
return func() {
if tools.Cfg.Campus.CampusProxy == nil || tools.Cfg.Campus.RelevantOrgs == nil {
return
}
var res []*model.PrefetchedCourse
for _, org := range *tools.Cfg.Campus.RelevantOrgs {
r, err := getCoursesForOrg(org)
if err != nil {
log.Error(err)
} else {
res = append(res, r...)
}
}
err := dao.PrefetchedCourseDao.Create(context.Background(), res...)
if err != nil {
log.Error(err)
}
}
}

func getCoursesForOrg(org string) ([]*model.PrefetchedCourse, error) {
conf := client.NewConfiguration()
conf.Host = "campus-proxy.mm.rbg.tum.de"
conf.Scheme = "https"
c := client.NewAPIClient(conf)
courses, _, err := c.OrganizationApi.
OrganizationCoursesGet(context.WithValue(context.Background(), client.ContextAPIKeys, map[string]client.APIKey{"ApiKeyAuth": {Key: tools.Cfg.Campus.Tokens[0]}})).
IncludeChildren(true).
OrgUnitID(org).
Execute()
if err.Error() != "" {
return nil, fmt.Errorf("load Course: %v", err.Error())
}
var res []*model.PrefetchedCourse
for _, c := range courses {
t := "W"
if strings.Contains(c.GetTeachingTerm(), "Sommer") {
t = "S"
}
if !strings.Contains(c.GetTeachingTerm(), " ") {
continue
}
y, err := strconv.Atoi(strings.Split(strings.Split(c.GetTeachingTerm(), " ")[1], "/")[0])
if err != nil {
continue
}
res = append(res, &model.PrefetchedCourse{
Name: c.CourseName.GetText(),
CourseID: c.GetCourseId(),
Term: t,
Year: y,
})
}
return res, nil
}
Loading

0 comments on commit 39c4e37

Please sign in to comment.