able to update desc for course
This commit is contained in:
@ -1,15 +1,21 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"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/kurious/app/command"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/app/query"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/service"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type courseServer struct {
|
||||
@ -108,7 +114,14 @@ func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
|
||||
|
||||
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
|
||||
}
|
||||
@ -139,3 +152,52 @@ func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.main-course {
|
||||
background-color: #3B4252;
|
||||
color: #E5E9F0;
|
||||
text-align: center;
|
||||
}
|
||||
.sub-course {
|
||||
background-color: #4C566A;
|
||||
color: #ECEFF4;
|
||||
}
|
||||
.course-plate {
|
||||
background-color: #f2f2f2;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.course-plate a {
|
||||
color: #333;
|
||||
@ -60,6 +68,14 @@ const listTemplate = `{{define "courses"}}
|
||||
.course-plate a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.editable-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
.editable-text.editing {
|
||||
border: 1px solid #000;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -73,27 +89,61 @@ const listTemplate = `{{define "courses"}}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<h1>Courses</h1>
|
||||
{{range $category := .Categories}}
|
||||
<h2>{{$category.Name}}</h2>
|
||||
<p>{{$category.Description}}</p>
|
||||
{{range $subcategory := .Subcategories}}
|
||||
<h2>{{$subcategory.Name}}</h2>
|
||||
<p>{{$subcategory.Description}}</p>
|
||||
{{range $course := $.Courses}}
|
||||
<div class="course-plate">
|
||||
<h3><a href="/courses/{{$course.ID}}">{{$course.Name}}</a></h3>
|
||||
<p>{{$course.Description}}</p>
|
||||
<p>Full price: {{$course.FullPrice}}</p>
|
||||
<p>Discount: {{$course.Discount}}</p>
|
||||
<p>Thematic: {{$course.Thematic}}</p>
|
||||
<p>Learning type: {{$course.LearningType}}</p>
|
||||
<p>Duration: {{$course.Duration}}</p>
|
||||
<p>Starts at: {{$course.StartsAt}}</p>
|
||||
</div>
|
||||
{{range $category := .Categories}}
|
||||
<h2 class="main-course">Category {{$category.Name}}</h2>
|
||||
<p> Course Description: {{$category.Description}}</p>
|
||||
{{range $subcategory := $category.Subcategories}}
|
||||
<div>
|
||||
<h2 class="sub-course"> Subcategory: {{$subcategory.Name}}</h2>
|
||||
<p>Subcategory Description: {{$subcategory.Description}}</p>
|
||||
{{range $course := $subcategory.Courses}}
|
||||
<div class="course-plate">
|
||||
<h3><a href="/courses/{{$course.ID}}">{{$course.Name}}</a></h3>
|
||||
<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>Discount: {{$course.Discount}}</p>
|
||||
<p>Thematic: {{$course.Thematic}}</p>
|
||||
<p>Learning type: {{$course.LearningType}}</p>
|
||||
<p>Duration: {{$course.Duration}}</p>
|
||||
<p>Starts at: {{$course.StartsAt}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{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>
|
||||
</html>
|
||||
{{end}}`
|
||||
|
||||
Reference in New Issue
Block a user