able to update desc for course
This commit is contained in:
@ -94,9 +94,13 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
|||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
coursesAPI := srv.Courses()
|
coursesAPI := srv.Courses()
|
||||||
|
|
||||||
|
router.Use(mux.CORSMethodMiddleware(router))
|
||||||
|
router.Use(middlewareLogger(log))
|
||||||
|
router.HandleFunc("/updatedesc", coursesAPI.UdpateDescription).Methods(http.MethodPost)
|
||||||
coursesRouter := router.PathPrefix("/courses").Subrouter()
|
coursesRouter := router.PathPrefix("/courses").Subrouter()
|
||||||
coursesRouter.Use(middlewareLogger(log))
|
coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
|
||||||
coursesRouter.HandleFunc("/", coursesAPI.List)
|
coursesRouter.HandleFunc("/{course_id}", coursesAPI.Get).Methods(http.MethodGet)
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Addr: cfg.ListenAddr,
|
Addr: cfg.ListenAddr,
|
||||||
|
|||||||
@ -151,12 +151,18 @@ func (r *ydbCourseRepository) List(
|
|||||||
appendTextParam("course_thematic", params.CourseThematic)
|
appendTextParam("course_thematic", params.CourseThematic)
|
||||||
appendTextParam("learning_type", params.LearningType)
|
appendTextParam("learning_type", params.LearningType)
|
||||||
|
|
||||||
|
opts = append(
|
||||||
|
opts,
|
||||||
|
table.ValueParam("$id", types.TextValue(params.NextPageToken)),
|
||||||
|
table.ValueParam("$limit", types.Int32Value(int32(params.Limit))),
|
||||||
|
)
|
||||||
|
|
||||||
query, err := qtParams.render()
|
query, err := qtParams.render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("rendering: %w", err)
|
return result, fmt.Errorf("rendering: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
xcontext.LogInfo(ctx, r.log, "planning to run query", slog.String("query", query), slog.Any("opts", opts))
|
xcontext.LogInfo(ctx, r.log, "planning to run query", slog.String("query", query), slog.String("opts", tableParamOptsToString(opts...)))
|
||||||
|
|
||||||
courses := make([]domain.Course, 0, 1_000)
|
courses := make([]domain.Course, 0, 1_000)
|
||||||
readTx := table.TxControl(
|
readTx := table.TxControl(
|
||||||
@ -319,10 +325,31 @@ func (r *ydbCourseRepository) GetByExternalID(ctx context.Context, id string) (d
|
|||||||
return domain.Course{}, nil
|
return domain.Course{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCourseParamsAsStruct(params domain.CreateCourseParams) types.Value {
|
type updateCourseParams struct {
|
||||||
st := mapSourceTypeFromDomain(params.SourceType)
|
domain.CreateCourseParams
|
||||||
|
|
||||||
|
CreatedAt time.Time
|
||||||
|
DeletedAt nullable.Value[time.Time]
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCourseParamsAsStruct(params updateCourseParams) types.Value {
|
||||||
|
opts := createCourseParamsAsStructValues(params.CreateCourseParams)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return types.StructValue(
|
return types.StructValue(
|
||||||
|
append(
|
||||||
|
opts[:len(opts)-3],
|
||||||
|
types.StructFieldValue("created_at", types.DatetimeValueFromTime(params.CreatedAt)),
|
||||||
|
types.StructFieldValue("updated_at", types.DatetimeValueFromTime(now)),
|
||||||
|
types.StructFieldValue("deleted_at", types.NullableDatetimeValue(nil)),
|
||||||
|
)...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCourseParamsAsStructValues(params domain.CreateCourseParams) []types.StructValueOption {
|
||||||
|
st := mapSourceTypeFromDomain(params.SourceType)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
return []types.StructValueOption{
|
||||||
types.StructFieldValue("id", types.TextValue(params.ID)),
|
types.StructFieldValue("id", types.TextValue(params.ID)),
|
||||||
types.StructFieldValue("name", types.TextValue(params.Name)),
|
types.StructFieldValue("name", types.TextValue(params.Name)),
|
||||||
types.StructFieldValue("source_type", types.TextValue(st)),
|
types.StructFieldValue("source_type", types.TextValue(st)),
|
||||||
@ -341,6 +368,12 @@ func createCourseParamsAsStruct(params domain.CreateCourseParams) types.Value {
|
|||||||
types.StructFieldValue("created_at", types.DatetimeValueFromTime(now)),
|
types.StructFieldValue("created_at", types.DatetimeValueFromTime(now)),
|
||||||
types.StructFieldValue("updated_at", types.DatetimeValueFromTime(now)),
|
types.StructFieldValue("updated_at", types.DatetimeValueFromTime(now)),
|
||||||
types.StructFieldValue("deleted_at", types.NullableDatetimeValue(nil)),
|
types.StructFieldValue("deleted_at", types.NullableDatetimeValue(nil)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCourseParamsAsStruct(params domain.CreateCourseParams) types.Value {
|
||||||
|
return types.StructValue(
|
||||||
|
createCourseParamsAsStructValues(params)...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,6 +457,100 @@ func (r *ydbCourseRepository) Delete(ctx context.Context, id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ydbCourseRepository) UpdateCourseDescription(ctx context.Context, id, description string) error {
|
||||||
|
course, err := r.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting course: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := updateCourseParams{
|
||||||
|
CreateCourseParams: domain.CreateCourseParams{
|
||||||
|
ID: course.ID,
|
||||||
|
ExternalID: course.ExternalID,
|
||||||
|
Name: course.Name,
|
||||||
|
SourceType: course.SourceType,
|
||||||
|
SourceName: course.SourceName,
|
||||||
|
CourseThematic: course.Thematic,
|
||||||
|
LearningType: course.LearningType,
|
||||||
|
OrganizationID: course.OrganizationID,
|
||||||
|
OriginLink: course.OriginLink,
|
||||||
|
ImageLink: course.ImageLink,
|
||||||
|
Description: description,
|
||||||
|
FullPrice: course.FullPrice,
|
||||||
|
Discount: course.Discount,
|
||||||
|
Duration: course.Duration,
|
||||||
|
StartsAt: course.StartsAt,
|
||||||
|
},
|
||||||
|
CreatedAt: course.CreatedAt,
|
||||||
|
DeletedAt: course.DeletedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStruct := updateCourseParamsAsStruct(params)
|
||||||
|
|
||||||
|
const upsertQuery = `DECLARE $courseData AS List<Struct<
|
||||||
|
id: Text,
|
||||||
|
external_id: Optional<Text>,
|
||||||
|
name: Text,
|
||||||
|
source_type: Text,
|
||||||
|
source_name: Optional<Text>,
|
||||||
|
course_thematic: Text,
|
||||||
|
learning_type: Text,
|
||||||
|
organization_id: Text,
|
||||||
|
origin_link: Text,
|
||||||
|
image_link: Text,
|
||||||
|
description: Text,
|
||||||
|
full_price: Double,
|
||||||
|
discount: Double,
|
||||||
|
duration: Interval,
|
||||||
|
starts_at: Datetime,
|
||||||
|
created_at: Datetime,
|
||||||
|
updated_at: Datetime,
|
||||||
|
deleted_at: Optional<Datetime>>>;
|
||||||
|
|
||||||
|
REPLACE INTO
|
||||||
|
courses
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
name,
|
||||||
|
source_type,
|
||||||
|
source_name,
|
||||||
|
course_thematic,
|
||||||
|
learning_type,
|
||||||
|
organization_id,
|
||||||
|
origin_link,
|
||||||
|
image_link,
|
||||||
|
description,
|
||||||
|
full_price,
|
||||||
|
discount,
|
||||||
|
duration,
|
||||||
|
starts_at,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
deleted_at
|
||||||
|
FROM AS_TABLE($courseData);`
|
||||||
|
|
||||||
|
writeTx := table.TxControl(
|
||||||
|
table.BeginTx(
|
||||||
|
table.WithSerializableReadWrite(),
|
||||||
|
),
|
||||||
|
table.CommitTx(),
|
||||||
|
)
|
||||||
|
err = r.db.Table().Do(ctx, func(ctx context.Context, s table.Session) error {
|
||||||
|
queryParams := table.NewQueryParameters(
|
||||||
|
table.ValueParam("$courseData", types.ListValue(updateStruct)),
|
||||||
|
)
|
||||||
|
_, _, err := s.Execute(ctx, writeTx, upsertQuery, queryParams)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ydbCourseRepository) CreateCourseTable(ctx context.Context) error {
|
func (r *ydbCourseRepository) CreateCourseTable(ctx context.Context) error {
|
||||||
return r.db.Table().Do(ctx, func(ctx context.Context, s table.Session) error {
|
return r.db.Table().Do(ctx, func(ctx context.Context, s table.Session) error {
|
||||||
return s.CreateTable(
|
return s.CreateTable(
|
||||||
@ -595,3 +722,12 @@ WHERE {{ range .Conditions }}{{.}}{{end}}
|
|||||||
{{.Suffix}}`
|
{{.Suffix}}`
|
||||||
|
|
||||||
var querySelect = template.Must(template.New("").Parse(queryTemplateSelect))
|
var querySelect = template.Must(template.New("").Parse(queryTemplateSelect))
|
||||||
|
|
||||||
|
func tableParamOptsToString(in ...table.ParameterOption) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, opt := range in {
|
||||||
|
sb.WriteString(opt.Name() + "(" + opt.Value().Type().String() + ");")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ type Commands struct {
|
|||||||
InsertCourses command.CreateCoursesHandler
|
InsertCourses command.CreateCoursesHandler
|
||||||
InsertCourse command.CreateCourseHandler
|
InsertCourse command.CreateCourseHandler
|
||||||
DeleteCourse command.DeleteCourseHandler
|
DeleteCourse command.DeleteCourseHandler
|
||||||
|
UpdateCourseDescription command.UpdateCourseDescriptionHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
type Queries struct {
|
type Queries struct {
|
||||||
|
|||||||
35
internal/kurious/app/command/updatecoursedescription.go
Normal file
35
internal/kurious/app/command/updatecoursedescription.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/decorator"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateCourseDescription struct {
|
||||||
|
ID string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCourseDescriptionHandler decorator.CommandHandler[UpdateCourseDescription]
|
||||||
|
|
||||||
|
type updateCourseDescriptionHandler struct {
|
||||||
|
repo domain.CourseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateCourseDescriptionHandler(
|
||||||
|
repo domain.CourseRepository,
|
||||||
|
log *slog.Logger,
|
||||||
|
) UpdateCourseDescriptionHandler {
|
||||||
|
h := updateCourseDescriptionHandler{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorator.ApplyCommandDecorators(h, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h updateCourseDescriptionHandler) Handle(ctx context.Context, cmd UpdateCourseDescription) error {
|
||||||
|
return h.repo.UpdateCourseDescription(ctx, cmd.ID, cmd.Description)
|
||||||
|
}
|
||||||
@ -56,6 +56,9 @@ type CourseRepository interface {
|
|||||||
Create(context.Context, CreateCourseParams) (Course, error)
|
Create(context.Context, CreateCourseParams) (Course, error)
|
||||||
// Delete course by id.
|
// Delete course by id.
|
||||||
Delete(ctx context.Context, id string) error
|
Delete(ctx context.Context, id string) error
|
||||||
|
|
||||||
|
// UpdateCourseDescription is a temporary method to udpate description field
|
||||||
|
UpdateCourseDescription(ctx context.Context, id string, description string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateOrganizationParams struct {
|
type CreateOrganizationParams struct {
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
"git.loyso.art/frx/kurious/internal/common/errors"
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||||
"git.loyso.art/frx/kurious/internal/common/xslice"
|
"git.loyso.art/frx/kurious/internal/common/xslice"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/app/command"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/app/query"
|
"git.loyso.art/frx/kurious/internal/kurious/app/query"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/service"
|
"git.loyso.art/frx/kurious/internal/kurious/service"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type courseServer struct {
|
type courseServer struct {
|
||||||
@ -108,7 +114,14 @@ func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
|
|||||||
|
|
||||||
outCategory.Subcategories = append(outCategory.Subcategories, outSubCategory)
|
outCategory.Subcategories = append(outCategory.Subcategories, outSubCategory)
|
||||||
}
|
}
|
||||||
|
sort.Slice(outCategory.Subcategories, func(i, j int) bool {
|
||||||
|
return outCategory.Subcategories[i].ID < outCategory.Subcategories[j].ID
|
||||||
|
})
|
||||||
|
out.Categories = append(out.Categories, outCategory)
|
||||||
}
|
}
|
||||||
|
sort.Slice(out.Categories, func(i, j int) bool {
|
||||||
|
return out.Categories[i].ID < out.Categories[j].ID
|
||||||
|
})
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@ -139,3 +152,52 @@ func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c courseServer) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
id := mux.Vars(r)["course_id"]
|
||||||
|
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
|
||||||
|
ID: id,
|
||||||
|
})
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to get course") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.MarshalIndent(course, "", " ")
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to marshal json") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("content-type", "application/json")
|
||||||
|
w.Header().Set("content-length", strconv.Itoa(len(payload)))
|
||||||
|
|
||||||
|
_, err = w.Write([]byte(payload))
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogWithWarnError(ctx, c.log, err, "unable to write a message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c courseServer) UdpateDescription(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type requestModel struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
var req requestModel
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to read body") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.app.Commands.UpdateCourseDescription.Handle(ctx, command.UpdateCourseDescription{
|
||||||
|
ID: req.ID,
|
||||||
|
Description: req.Text,
|
||||||
|
})
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to update course description") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|||||||
60
internal/kurious/ports/http/course_test.go
Normal file
60
internal/kurious/ports/http/course_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
var courses = func() []domain.Course {
|
||||||
|
out := make([]domain.Course, 0)
|
||||||
|
out = append(out, makeBatchCourses("prog", []string{"go", "rust"}, 4)...)
|
||||||
|
out = append(out, makeBatchCourses("front", []string{"js", "html"}, 4)...)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}()
|
||||||
|
|
||||||
|
func makeBatchCourses(lt string, cts []string, num int) []domain.Course {
|
||||||
|
out := make([]domain.Course, 0, len(cts)*num)
|
||||||
|
for _, ct := range cts {
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
name := strings.Join([]string{
|
||||||
|
lt, ct,
|
||||||
|
strconv.Itoa(i),
|
||||||
|
}, ".")
|
||||||
|
out = append(out, makeCourse(lt, ct, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCourse(lt, ct, name string) domain.Course {
|
||||||
|
return domain.Course{
|
||||||
|
LearningType: lt,
|
||||||
|
Thematic: ct,
|
||||||
|
Name: name,
|
||||||
|
ID: lt + ct + name,
|
||||||
|
FullPrice: 123,
|
||||||
|
Duration: time.Second * 100,
|
||||||
|
StartsAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderTemplate(t *testing.T) {
|
||||||
|
t.SkipNow()
|
||||||
|
result := mapDomainCourseToTemplate(courses...)
|
||||||
|
t.Logf("%#v", result)
|
||||||
|
|
||||||
|
var out strings.Builder
|
||||||
|
err := listTemplateParsed.ExecuteTemplate(&out, "courses", result)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("executing: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(out.String())
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
@ -45,13 +45,21 @@ const listTemplate = `{{define "courses"}}
|
|||||||
p {
|
p {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.main-course {
|
||||||
|
background-color: #3B4252;
|
||||||
|
color: #E5E9F0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.sub-course {
|
||||||
|
background-color: #4C566A;
|
||||||
|
color: #ECEFF4;
|
||||||
|
}
|
||||||
.course-plate {
|
.course-plate {
|
||||||
background-color: #f2f2f2;
|
background-color: #f2f2f2;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
.course-plate a {
|
.course-plate a {
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -60,6 +68,14 @@ const listTemplate = `{{define "courses"}}
|
|||||||
.course-plate a:hover {
|
.course-plate a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
.editable-text {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.editable-text.editing {
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -73,17 +89,17 @@ const listTemplate = `{{define "courses"}}
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<h1>Courses</h1>
|
|
||||||
{{range $category := .Categories}}
|
{{range $category := .Categories}}
|
||||||
<h2>{{$category.Name}}</h2>
|
<h2 class="main-course">Category {{$category.Name}}</h2>
|
||||||
<p>{{$category.Description}}</p>
|
<p> Course Description: {{$category.Description}}</p>
|
||||||
{{range $subcategory := .Subcategories}}
|
{{range $subcategory := $category.Subcategories}}
|
||||||
<h2>{{$subcategory.Name}}</h2>
|
<div>
|
||||||
<p>{{$subcategory.Description}}</p>
|
<h2 class="sub-course"> Subcategory: {{$subcategory.Name}}</h2>
|
||||||
{{range $course := $.Courses}}
|
<p>Subcategory Description: {{$subcategory.Description}}</p>
|
||||||
|
{{range $course := $subcategory.Courses}}
|
||||||
<div class="course-plate">
|
<div class="course-plate">
|
||||||
<h3><a href="/courses/{{$course.ID}}">{{$course.Name}}</a></h3>
|
<h3><a href="/courses/{{$course.ID}}">{{$course.Name}}</a></h3>
|
||||||
<p>{{$course.Description}}</p>
|
<p>Description: <div id="editable-text-{{$course.ID}}" class="editable-text" contenteditable=false>{{or $course.Description "..."}}</div></p>
|
||||||
<p>Full price: {{$course.FullPrice}}</p>
|
<p>Full price: {{$course.FullPrice}}</p>
|
||||||
<p>Discount: {{$course.Discount}}</p>
|
<p>Discount: {{$course.Discount}}</p>
|
||||||
<p>Thematic: {{$course.Thematic}}</p>
|
<p>Thematic: {{$course.Thematic}}</p>
|
||||||
@ -91,9 +107,43 @@ const listTemplate = `{{define "courses"}}
|
|||||||
<p>Duration: {{$course.Duration}}</p>
|
<p>Duration: {{$course.Duration}}</p>
|
||||||
<p>Starts at: {{$course.StartsAt}}</p>
|
<p>Starts at: {{$course.StartsAt}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const editableTexts = document.querySelectorAll('.editable-text');
|
||||||
|
let isEditing = false;
|
||||||
|
|
||||||
|
editableTexts.forEach(function(editableText) {
|
||||||
|
editableText.addEventListener('click', function() {
|
||||||
|
if (!isEditing) {
|
||||||
|
editableText.contentEditable = 'true';
|
||||||
|
editableText.className += ' editing';
|
||||||
|
isEditing = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editableText.addEventListener('keydown', function(event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
const text = editableText.innerText;
|
||||||
|
const id = editableText.id.replace('editable-text-', ''); // Extract the ID from the element's ID
|
||||||
|
|
||||||
|
// Send a POST request with JSON data
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/updatedesc', true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
xhr.send(JSON.stringify({ text, id }));
|
||||||
|
|
||||||
|
editableText.contentEditable = 'false';
|
||||||
|
editableText.className = 'editable-text';
|
||||||
|
isEditing = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{end}}`
|
{{end}}`
|
||||||
|
|||||||
@ -39,6 +39,7 @@ func NewApplication(ctx context.Context, cfg ApplicationConfig) (Application, er
|
|||||||
InsertCourses: command.NewCreateCoursesHandler(courseadapter, log),
|
InsertCourses: command.NewCreateCoursesHandler(courseadapter, log),
|
||||||
InsertCourse: command.NewCreateCourseHandler(courseadapter, log),
|
InsertCourse: command.NewCreateCourseHandler(courseadapter, log),
|
||||||
DeleteCourse: command.NewDeleteCourseHandler(courseadapter, log),
|
DeleteCourse: command.NewDeleteCourseHandler(courseadapter, log),
|
||||||
|
UpdateCourseDescription: command.NewUpdateCourseDescriptionHandler(courseadapter, log),
|
||||||
},
|
},
|
||||||
Queries: app.Queries{
|
Queries: app.Queries{
|
||||||
GetCourse: query.NewGetCourseHandler(courseadapter, log),
|
GetCourse: query.NewGetCourseHandler(courseadapter, log),
|
||||||
|
|||||||
Reference in New Issue
Block a user