Compare commits
19 Commits
d5f76f4e07
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 348c737163 | |||
| c3d6510a7d | |||
| c7fada2c54 | |||
| c0f45d98c2 | |||
| 1a31006b21 | |||
| e8ac96a065 | |||
| 3a9e01a683 | |||
| 035e9c848f | |||
| 605e117586 | |||
| 68810d93a7 | |||
| e7c2832865 | |||
| 9d2efcc1c4 | |||
| 88a3cae4fa | |||
| 938d3cd307 | |||
| 97986063df | |||
| 251ecd94d4 | |||
| 6d1769ff24 | |||
| af4a4f7840 | |||
| e5dfccabbf |
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +1,2 @@
|
|||||||
./assets/kurious binary
|
./assets/kurious binary
|
||||||
|
*/**/*_templ.go binary
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
*.json
|
*.json
|
||||||
bin
|
bin
|
||||||
./tags
|
./tags
|
||||||
|
*.sqlite
|
||||||
|
.zed
|
||||||
|
*.log
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
with-expecter: true
|
with-expecter: true
|
||||||
keeptree: True
|
keeptree: True
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
cf55887b91f81f789d59205c41f8368
|
d65622032d35cb78ee1539f4ab4d875b
|
||||||
|
|||||||
22
Taskfile.yml
22
Taskfile.yml
@ -1,4 +1,4 @@
|
|||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
@ -17,21 +17,34 @@ vars:
|
|||||||
tasks:
|
tasks:
|
||||||
install_tools:
|
install_tools:
|
||||||
cmds:
|
cmds:
|
||||||
- go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
|
- "[[ ! -f $GOBIN/golangci-lint ]] && go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 || echo golang-ci lint installed"
|
||||||
- go install github.com/a-h/templ/cmd/templ@v0.2.513
|
- "rm -rf $GOBIN/templ && go install github.com/a-h/templ/cmd/templ@v0.2.707 || echo templ installed"
|
||||||
|
- "[[ ! -f $GOBIN/mockery ]] && go install github.com/vektra/mockery/v2@v2.42.1 || echo mockery installed"
|
||||||
generate:
|
generate:
|
||||||
|
run: once
|
||||||
cmds:
|
cmds:
|
||||||
- "$GOBIN/templ generate"
|
- "$GOBIN/templ generate"
|
||||||
sources:
|
sources:
|
||||||
- "internal/kurious/ports/http/templ/*.templ"
|
- "internal/kurious/ports/http/templ/*.templ"
|
||||||
|
- "internal/kurious/ports/http/bootstrap/*.templ"
|
||||||
generates:
|
generates:
|
||||||
- "internal/kurious/ports/http/templ/*.go"
|
- "internal/kurious/ports/http/templ/*.go"
|
||||||
|
- "internal/kurious/ports/http/bootstrap/*.go"
|
||||||
|
deps:
|
||||||
|
- install_tools
|
||||||
|
mocks:
|
||||||
|
run: once
|
||||||
|
cmd: "go generate ./internal/..."
|
||||||
|
deps:
|
||||||
|
- install_tools
|
||||||
check:
|
check:
|
||||||
|
run: once
|
||||||
cmds:
|
cmds:
|
||||||
- "$GOBIN/golangci-lint run ./..."
|
- "$GOBIN/golangci-lint run ./..."
|
||||||
deps:
|
deps:
|
||||||
- generate
|
- generate
|
||||||
test:
|
test:
|
||||||
|
run: once
|
||||||
cmds:
|
cmds:
|
||||||
- go test ./internal/...
|
- go test ./internal/...
|
||||||
deps:
|
deps:
|
||||||
@ -52,8 +65,9 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- task: build_dev_cli
|
- task: build_dev_cli
|
||||||
- task: build_background
|
- task: build_background
|
||||||
|
- task: build_web
|
||||||
|
|
||||||
run:
|
run:
|
||||||
deps: [build]
|
deps: [build]
|
||||||
cmds:
|
cmds:
|
||||||
- $GOBIN/sravnicli
|
- $GOBIN/kuriousweb
|
||||||
|
|||||||
@ -8,9 +8,19 @@ import (
|
|||||||
"git.loyso.art/frx/kurious/internal/common/config"
|
"git.loyso.art/frx/kurious/internal/common/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type dbEngine string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DBEngineUnknown dbEngine = ""
|
||||||
|
DBEngineYDB dbEngine = "ydb"
|
||||||
|
DBEngineSqlite dbEngine = "sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Log config.Log `json:"log"`
|
Log config.Log `json:"log"`
|
||||||
YDB config.YDB `json:"ydb"`
|
YDB config.YDB `json:"ydb"`
|
||||||
|
Sqlite config.Sqlite `json:"sqlite"`
|
||||||
|
DBEngine dbEngine `json:"db_engine"`
|
||||||
SyncSravniCron string `json:"sync_sravni_cron"`
|
SyncSravniCron string `json:"sync_sravni_cron"`
|
||||||
|
|
||||||
DebugHTTP bool `json:"debug_http"`
|
DebugHTTP bool `json:"debug_http"`
|
||||||
@ -37,5 +47,7 @@ func defaultConfig() Config {
|
|||||||
Level: config.LogLevelInfo,
|
Level: config.LogLevelInfo,
|
||||||
Format: config.LogFormatText,
|
Format: config.LogFormatText,
|
||||||
},
|
},
|
||||||
|
// TODO: change to sqlite once it proven to be working
|
||||||
|
DBEngine: DBEngineYDB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const savingOrganizationIDInternalInsteadOfExternal = false
|
||||||
|
|
||||||
func app(ctx context.Context) error {
|
func app(ctx context.Context) error {
|
||||||
|
if !savingOrganizationIDInternalInsteadOfExternal {
|
||||||
|
panic("fix saving ogranization id as external id instead of internal")
|
||||||
|
}
|
||||||
|
|
||||||
var cfgpath string
|
var cfgpath string
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
cfgpath = os.Args[1]
|
cfgpath = os.Args[1]
|
||||||
@ -69,9 +75,19 @@ func app(ctx context.Context) error {
|
|||||||
|
|
||||||
mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped)
|
mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped)
|
||||||
|
|
||||||
|
var dbEngine service.RepositoryEngine
|
||||||
|
switch cfg.DBEngine {
|
||||||
|
case DBEngineSqlite:
|
||||||
|
dbEngine = service.RepositoryEngineSqlite
|
||||||
|
case DBEngineYDB:
|
||||||
|
dbEngine = service.RepositoryEngineYDB
|
||||||
|
}
|
||||||
|
|
||||||
app, err := service.NewApplication(ctx, service.ApplicationConfig{
|
app, err := service.NewApplication(ctx, service.ApplicationConfig{
|
||||||
LogConfig: cfg.Log,
|
LogConfig: cfg.Log,
|
||||||
YDB: cfg.YDB,
|
YDB: cfg.YDB,
|
||||||
|
Sqlite: cfg.Sqlite,
|
||||||
|
Engine: dbEngine,
|
||||||
}, mapper)
|
}, mapper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making new application: %w", err)
|
return fmt.Errorf("making new application: %w", err)
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
println("oh well")
|
|
||||||
}
|
|
||||||
@ -61,6 +61,7 @@ func setupCLI(ctx context.Context) cli.App {
|
|||||||
case "courses":
|
case "courses":
|
||||||
out = state.Props.InitialReduxState.Dictionaries.Data.CourseThematics
|
out = state.Props.InitialReduxState.Dictionaries.Data.CourseThematics
|
||||||
}
|
}
|
||||||
|
|
||||||
log.InfoContext(ctx, "loaded state", slog.Any("state", out))
|
log.InfoContext(ctx, "loaded state", slog.Any("state", out))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@ -44,7 +44,6 @@ func setupAPICommand(ctx context.Context) cli.Command {
|
|||||||
WithOption(learningSelectionOpt).
|
WithOption(learningSelectionOpt).
|
||||||
WithAction(newProductsFilterCountAction(ctx))
|
WithAction(newProductsFilterCountAction(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
apiEducation := cli.NewCommand("education", "Education related category").
|
apiEducation := cli.NewCommand("education", "Education related category").
|
||||||
WithCommand(apiEducationListProducts).
|
WithCommand(apiEducationListProducts).
|
||||||
WithCommand(apiEducationFilterCount)
|
WithCommand(apiEducationFilterCount)
|
||||||
@ -137,10 +136,13 @@ func (a *listProductsAction) parse(args []string, options map[string]string) err
|
|||||||
func (a *listProductsAction) handle() error {
|
func (a *listProductsAction) handle() error {
|
||||||
params := sravni.ListEducationProductsParams{
|
params := sravni.ListEducationProductsParams{
|
||||||
LearningType: a.params.learningType,
|
LearningType: a.params.learningType,
|
||||||
CoursesThematics: []string{a.params.courseThematic},
|
|
||||||
Limit: a.params.limit,
|
Limit: a.params.limit,
|
||||||
Offset: a.params.offset,
|
Offset: a.params.offset,
|
||||||
}
|
}
|
||||||
|
if a.params.courseThematic != "" {
|
||||||
|
params.CoursesThematics = append(params.CoursesThematics, a.params.courseThematic)
|
||||||
|
}
|
||||||
|
|
||||||
result, err := a.client.ListEducationalProducts(a.ctx, params)
|
result, err := a.client.ListEducationalProducts(a.ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listing education products: %w", err)
|
return fmt.Errorf("listing education products: %w", err)
|
||||||
|
|||||||
@ -11,7 +11,10 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Log config.Log `json:"log"`
|
Log config.Log `json:"log"`
|
||||||
YDB config.YDB `json:"ydb"`
|
YDB config.YDB `json:"ydb"`
|
||||||
|
Sqlite config.Sqlite `json:"sqlite"`
|
||||||
HTTP config.HTTP `json:"http"`
|
HTTP config.HTTP `json:"http"`
|
||||||
|
Tracing config.Trace `json:"tracing"`
|
||||||
|
DBEngine string `json:"db_engine"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFromFile(path string, defaultConfigF func() Config) (Config, error) {
|
func readFromFile(path string, defaultConfigF func() Config) (Config, error) {
|
||||||
|
|||||||
@ -11,7 +11,14 @@ import (
|
|||||||
"git.loyso.art/frx/kurious/internal/common/generator"
|
"git.loyso.art/frx/kurious/internal/common/generator"
|
||||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||||
xhttp "git.loyso.art/frx/kurious/internal/kurious/ports/http"
|
xhttp "git.loyso.art/frx/kurious/internal/kurious/ports/http"
|
||||||
|
"git.loyso.art/frx/kurious/pkg/xdefault"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makePathTemplate(params ...string) string {
|
func makePathTemplate(params ...string) string {
|
||||||
@ -28,50 +35,33 @@ func makePathTemplate(params ...string) string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTTPWithTempl(srv xhttp.Server, router *mux.Router, log *slog.Logger) {
|
func setupCoursesHTTP(srv xhttp.Server, router *mux.Router, _ *slog.Logger) {
|
||||||
coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true)
|
|
||||||
|
|
||||||
coursesAPI := srv.CoursesByTempl()
|
|
||||||
|
|
||||||
coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
|
|
||||||
coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
|
|
||||||
coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet)
|
|
||||||
coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam)
|
|
||||||
coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupHTTPWithGoTemplates(srv xhttp.Server, router *mux.Router, log *slog.Logger) {
|
|
||||||
coursesAPI := srv.Courses()
|
coursesAPI := srv.Courses()
|
||||||
|
|
||||||
coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true)
|
router.Handle("/", http.RedirectHandler("/courses", http.StatusPermanentRedirect))
|
||||||
coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet)
|
|
||||||
coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
|
|
||||||
coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet)
|
|
||||||
coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam)
|
|
||||||
coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet)
|
|
||||||
|
|
||||||
courseRouter := router.PathPrefix("/course").PathPrefix("/{course_id}").Subrouter()
|
coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true)
|
||||||
courseRouter.HandleFunc("/", coursesAPI.Get).Methods(http.MethodGet)
|
|
||||||
courseRouter.HandleFunc("/short", coursesAPI.GetShort).Methods(http.MethodGet)
|
coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam)
|
||||||
courseRouter.HandleFunc("/editdesc", coursesAPI.RenderEditDescription).Methods(http.MethodGet)
|
coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam)
|
||||||
courseRouter.HandleFunc("/description", coursesAPI.UpdateCourseDescription).Methods(http.MethodPut)
|
|
||||||
|
muxHandleFunc(coursesRouter, "index", "/", coursesAPI.Index).Methods(http.MethodGet)
|
||||||
|
muxHandleFunc(coursesRouter, "list_learning", coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet)
|
||||||
|
muxHandleFunc(coursesRouter, "list_full", coursesListFullPath, coursesAPI.List).Methods(http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server {
|
func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
router.Use(
|
||||||
w.WriteHeader(http.StatusOK)
|
middlewareCustomWriterInjector(),
|
||||||
})
|
mux.CORSMethodMiddleware(router),
|
||||||
|
middlewareLogger(log),
|
||||||
|
middlewareTrace(),
|
||||||
|
middlewareMetrics(),
|
||||||
|
)
|
||||||
|
|
||||||
router.Use(mux.CORSMethodMiddleware(router))
|
setupCoursesHTTP(srv, router, log)
|
||||||
router.Use(middlewareLogger(log, cfg.Engine))
|
|
||||||
|
|
||||||
if cfg.Engine == "templ" {
|
|
||||||
setupHTTPWithTempl(srv, router, log)
|
|
||||||
} else {
|
|
||||||
setupHTTPWithGoTemplates(srv, router, log)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.MountLive {
|
if cfg.MountLive {
|
||||||
fs := http.FileServer(http.Dir("./assets/kurious/static/"))
|
fs := http.FileServer(http.Dir("./assets/kurious/static/"))
|
||||||
@ -102,7 +92,7 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fs := kurious.AsHTTPFileHandler()
|
fs := kurious.AsHTTPFileHandler()
|
||||||
router.PathPrefix("/").Handler(fs).Methods(http.MethodGet)
|
router.PathPrefix("/*").Handler(fs).Methods(http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
@ -111,7 +101,112 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc {
|
func middlewareCustomWriterInjector() mux.MiddlewareFunc {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
wr := wrapWithCustomWriter(w)
|
||||||
|
next.ServeHTTP(wr, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type attributeStringKey string
|
||||||
|
|
||||||
|
func (k attributeStringKey) Value(value string) attribute.KeyValue {
|
||||||
|
return attribute.String(string(k), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func must[T any](value T, err error) T {
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func middlewareMetrics() mux.MiddlewareFunc {
|
||||||
|
requestDuration := must(webmetric.Float64Histogram(
|
||||||
|
semconv.HTTPServerRequestDurationName,
|
||||||
|
metric.WithUnit(semconv.HTTPServerRequestDurationUnit),
|
||||||
|
metric.WithDescription(semconv.HTTPServerRequestDurationDescription),
|
||||||
|
))
|
||||||
|
|
||||||
|
f := func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
hpath, _ := route.GetPathTemplate()
|
||||||
|
|
||||||
|
attributes := make([]attribute.KeyValue, 0, 8)
|
||||||
|
attributes = append(
|
||||||
|
attributes,
|
||||||
|
semconv.HTTPRequestMethodOriginal(r.Method),
|
||||||
|
semconv.URLFull(hpath),
|
||||||
|
semconv.URLPath(r.URL.Path),
|
||||||
|
)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
if wr, ok := w.(*customResponseWriter); ok {
|
||||||
|
statusCode := xdefault.WithFallback(wr.statusCode, http.StatusOK)
|
||||||
|
attributes = append(attributes,
|
||||||
|
semconv.HTTPResponseStatusCode(statusCode),
|
||||||
|
semconv.HTTPResponseBodySize(wr.wroteBytes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
elapsed := time.Since(start).Truncate(time.Millisecond)
|
||||||
|
requestDuration.Record(
|
||||||
|
r.Context(), elapsed.Seconds(), metric.WithAttributes(attributes...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func middlewareTrace() mux.MiddlewareFunc {
|
||||||
|
reqidAttr := attributeStringKey("http.request_id")
|
||||||
|
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
reqid := xcontext.GetRequestID(ctx)
|
||||||
|
|
||||||
|
var span trace.Span
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
hpath, _ := route.GetPathTemplate()
|
||||||
|
ctx, span = webtracer.Start(
|
||||||
|
ctx, r.Method+" "+hpath,
|
||||||
|
trace.WithAttributes(
|
||||||
|
reqidAttr.Value(reqid),
|
||||||
|
semconv.HTTPRequestMethodOriginal(r.Method),
|
||||||
|
semconv.URLFull(hpath),
|
||||||
|
semconv.URLPath(r.URL.Path),
|
||||||
|
semconv.URLQuery(r.URL.RawQuery),
|
||||||
|
semconv.UserAgentOriginal(r.UserAgent()),
|
||||||
|
),
|
||||||
|
trace.WithSpanKind(trace.SpanKindServer),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
|
||||||
|
if wr, ok := w.(*customResponseWriter); ok {
|
||||||
|
statusCode := xdefault.WithFallback(wr.statusCode, http.StatusOK)
|
||||||
|
span.SetAttributes(
|
||||||
|
semconv.HTTPResponseStatusCode(statusCode),
|
||||||
|
semconv.HTTPResponseBodySize(wr.wroteBytes),
|
||||||
|
)
|
||||||
|
if statusCode > 399 {
|
||||||
|
span.SetStatus(codes.Error, "error during request")
|
||||||
|
} else {
|
||||||
|
span.SetStatus(codes.Ok, "request completed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func middlewareLogger(log *slog.Logger) mux.MiddlewareFunc {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
@ -119,11 +214,12 @@ func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc {
|
|||||||
if requestID == "" {
|
if requestID == "" {
|
||||||
requestID = generator.RandomInt64ID()
|
requestID = generator.RandomInt64ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = xcontext.WithLogFields(
|
ctx = xcontext.WithLogFields(
|
||||||
ctx,
|
ctx,
|
||||||
slog.String("request_id", requestID),
|
slog.String("request_id", requestID),
|
||||||
slog.String("engine", engine),
|
|
||||||
)
|
)
|
||||||
|
ctx = xcontext.WithRequestID(ctx, requestID)
|
||||||
|
|
||||||
xcontext.LogInfo(
|
xcontext.LogInfo(
|
||||||
ctx, log, "incoming request",
|
ctx, log, "incoming request",
|
||||||
@ -133,12 +229,45 @@ func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc {
|
|||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
elapsed := time.Since(start).Truncate(time.Millisecond)
|
elapsed := slog.Duration("elapsed", time.Since(start).Truncate(time.Millisecond))
|
||||||
|
|
||||||
xcontext.LogInfo(
|
logfields := make([]slog.Attr, 0, 3)
|
||||||
ctx, log, "request processed",
|
logfields = append(logfields, elapsed)
|
||||||
slog.Duration("elapsed", elapsed),
|
if wr, ok := w.(*customResponseWriter); ok {
|
||||||
|
statusCode := xdefault.WithFallback(wr.statusCode, http.StatusOK)
|
||||||
|
logfields = append(
|
||||||
|
logfields,
|
||||||
|
slog.Int("status_code", statusCode),
|
||||||
|
slog.Int("bytes_wrote", wr.wroteBytes),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
xcontext.LogInfo(ctx, log, "request processed", logfields...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wrapWithCustomWriter(origin http.ResponseWriter) *customResponseWriter {
|
||||||
|
return &customResponseWriter{
|
||||||
|
ResponseWriter: origin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type customResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
|
||||||
|
statusCode int
|
||||||
|
wroteBytes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *customResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
w.statusCode = statusCode
|
||||||
|
w.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *customResponseWriter) Write(data []byte) (n int, err error) {
|
||||||
|
n, err = w.ResponseWriter.Write(data)
|
||||||
|
w.wroteBytes += n
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|||||||
@ -46,6 +46,17 @@ func app(ctx context.Context) error {
|
|||||||
|
|
||||||
log := config.NewSLogger(cfg.Log)
|
log := config.NewSLogger(cfg.Log)
|
||||||
|
|
||||||
|
shutdownOtel, err := setupOtelSDK(ctx, cfg.Tracing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting up otel sdk: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := shutdownOtel(ctx)
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogWithError(ctx, log, err, "shutting down sdk")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
sravniClient, err := sravni.NewClient(ctx, log, false)
|
sravniClient, err := sravni.NewClient(ctx, log, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to make new sravni client: %w", err)
|
return fmt.Errorf("unable to make new sravni client: %w", err)
|
||||||
@ -71,9 +82,20 @@ func app(ctx context.Context) error {
|
|||||||
|
|
||||||
mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped)
|
mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped)
|
||||||
|
|
||||||
|
var dbengine service.RepositoryEngine
|
||||||
|
switch cfg.DBEngine {
|
||||||
|
case "ydb":
|
||||||
|
dbengine = service.RepositoryEngineYDB
|
||||||
|
case "sqlite":
|
||||||
|
dbengine = service.RepositoryEngineSqlite
|
||||||
|
default:
|
||||||
|
dbengine = service.RepositoryEngineUnknown
|
||||||
|
}
|
||||||
app, err := service.NewApplication(ctx, service.ApplicationConfig{
|
app, err := service.NewApplication(ctx, service.ApplicationConfig{
|
||||||
LogConfig: cfg.Log,
|
LogConfig: cfg.Log,
|
||||||
YDB: cfg.YDB,
|
YDB: cfg.YDB,
|
||||||
|
Sqlite: cfg.Sqlite,
|
||||||
|
Engine: dbengine,
|
||||||
}, mapper)
|
}, mapper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making new application: %w", err)
|
return fmt.Errorf("making new application: %w", err)
|
||||||
@ -113,6 +135,11 @@ func app(ctx context.Context) error {
|
|||||||
|
|
||||||
xcontext.LogInfo(ctx, log, "server closed successfuly")
|
xcontext.LogInfo(ctx, log, "server closed successfuly")
|
||||||
|
|
||||||
|
err = shutdownOtel(sdctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("shutting down sdk: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
243
cmd/kuriweb/trace.go
Normal file
243
cmd/kuriweb/trace.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/config"
|
||||||
|
"google.golang.org/grpc/encoding/gzip"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
webtracer = otel.Tracer("kuriweb.http")
|
||||||
|
webmetric = otel.Meter("kuriweb.http")
|
||||||
|
)
|
||||||
|
|
||||||
|
type shutdownFunc func(context.Context) error
|
||||||
|
|
||||||
|
func setupOtelSDK(ctx context.Context, cfg config.Trace) (shutdown shutdownFunc, err error) {
|
||||||
|
var shutdownFuncs []shutdownFunc
|
||||||
|
|
||||||
|
shutdown = func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
for _, f := range shutdownFuncs {
|
||||||
|
err = errors.Join(err, f(ctx))
|
||||||
|
}
|
||||||
|
shutdownFuncs = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError := func(inErr error) error {
|
||||||
|
err = errors.Join(inErr, shutdown(ctx))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resource, err := makeServiceResource(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return shutdown, fmt.Errorf("making service resource: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := newPropagator()
|
||||||
|
otel.SetTextMapPropagator(prop)
|
||||||
|
|
||||||
|
tracerProvider, err := newCommonTraceProvider(ctx, TraceProviderParams{
|
||||||
|
Endpoint: cfg.Endpoint,
|
||||||
|
Type: cfg.Type,
|
||||||
|
AuthHeader: cfg.APIHeader,
|
||||||
|
APIKey: cfg.APIKey,
|
||||||
|
}, resource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, handleError(err)
|
||||||
|
}
|
||||||
|
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
|
||||||
|
otel.SetTracerProvider(tracerProvider)
|
||||||
|
|
||||||
|
if cfg.ShowMetrics {
|
||||||
|
meterProvider, err := newCommonMeterProvider(ctx, meterProviderParams{
|
||||||
|
Endpoint: cfg.Endpoint,
|
||||||
|
Type: cfg.Type,
|
||||||
|
AuthHeaderKey: cfg.APIHeader,
|
||||||
|
AuthHeaderValue: cfg.APIKey,
|
||||||
|
ReadInterval: time.Second * 15,
|
||||||
|
}, resource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, handleError(err)
|
||||||
|
}
|
||||||
|
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
|
||||||
|
otel.SetMeterProvider(meterProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
return shutdown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPropagator() propagation.TextMapPropagator {
|
||||||
|
return propagation.NewCompositeTextMapPropagator(
|
||||||
|
propagation.TraceContext{},
|
||||||
|
propagation.Baggage{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TraceProviderParams struct {
|
||||||
|
Endpoint string
|
||||||
|
APIKey string
|
||||||
|
AuthHeader string
|
||||||
|
Type config.TraceClientType
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServiceResource(ctx context.Context) (*resource.Resource, error) {
|
||||||
|
r, err := resource.New(
|
||||||
|
ctx,
|
||||||
|
resource.WithDetectors(
|
||||||
|
resource.StringDetector(semconv.SchemaURL, semconv.ServiceNameKey, func() (string, error) {
|
||||||
|
return "bigstats:kuriweb", nil
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
resource.WithHost(),
|
||||||
|
resource.WithAttributes(
|
||||||
|
semconv.ServiceName("bigstats:kuriweb"),
|
||||||
|
semconv.DeploymentEnvironment("production"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("making new resource: %w", err)
|
||||||
|
}
|
||||||
|
return resource.Merge(resource.Default(), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommonTraceProvider(ctx context.Context, params TraceProviderParams, r *resource.Resource) (tp *trace.TracerProvider, err error) {
|
||||||
|
opts := make([]trace.TracerProviderOption, 0, 4)
|
||||||
|
opts = append(
|
||||||
|
opts,
|
||||||
|
trace.WithSampler(trace.AlwaysSample()),
|
||||||
|
trace.WithResource(r),
|
||||||
|
)
|
||||||
|
|
||||||
|
if params.Type != config.TraceClientTypeUnset {
|
||||||
|
var spanExporter trace.SpanExporter
|
||||||
|
var headers map[string]string
|
||||||
|
|
||||||
|
if params.AuthHeader != "" {
|
||||||
|
headers = make(map[string]string, 1)
|
||||||
|
headers[params.AuthHeader] = params.APIKey
|
||||||
|
}
|
||||||
|
|
||||||
|
switch params.Type {
|
||||||
|
case config.TraceClientTypeGRPC:
|
||||||
|
spanExporter, err = otlptracegrpc.New(
|
||||||
|
ctx,
|
||||||
|
otlptracegrpc.WithEndpointURL(params.Endpoint),
|
||||||
|
otlptracegrpc.WithInsecure(),
|
||||||
|
otlptracegrpc.WithHeaders(headers),
|
||||||
|
otlptracegrpc.WithCompressor(gzip.Name),
|
||||||
|
)
|
||||||
|
case config.TraceClientTypeHTTP:
|
||||||
|
httpClient := otlptracehttp.NewClient(
|
||||||
|
otlptracehttp.WithEndpointURL(params.Endpoint),
|
||||||
|
otlptracehttp.WithHeaders(headers),
|
||||||
|
otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
|
||||||
|
)
|
||||||
|
spanExporter, err = otlptrace.New(
|
||||||
|
ctx, httpClient,
|
||||||
|
)
|
||||||
|
case config.TraceClientTypeStdout:
|
||||||
|
spanExporter, err = stdouttrace.New(stdouttrace.WithPrettyPrint())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported provider type")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("making trace exporter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, trace.WithBatcher(spanExporter))
|
||||||
|
}
|
||||||
|
|
||||||
|
tp = trace.NewTracerProvider(opts...)
|
||||||
|
|
||||||
|
return tp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type meterProviderParams struct {
|
||||||
|
Endpoint string
|
||||||
|
AuthHeaderKey string
|
||||||
|
AuthHeaderValue string
|
||||||
|
ReadInterval time.Duration
|
||||||
|
Type config.TraceClientType
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommonMeterProvider(ctx context.Context, params meterProviderParams, r *resource.Resource) (*metric.MeterProvider, error) {
|
||||||
|
var exporter metric.Exporter
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch params.Type {
|
||||||
|
case config.TraceClientTypeGRPC:
|
||||||
|
headers := make(map[string]string, 1)
|
||||||
|
if params.AuthHeaderKey != "" {
|
||||||
|
headers[params.AuthHeaderKey] = params.AuthHeaderValue
|
||||||
|
}
|
||||||
|
|
||||||
|
exporter, err = otlpmetricgrpc.New(
|
||||||
|
ctx,
|
||||||
|
otlpmetricgrpc.WithEndpointURL(params.Endpoint),
|
||||||
|
otlpmetricgrpc.WithHeaders(headers),
|
||||||
|
otlpmetricgrpc.WithCompressor(gzip.Name),
|
||||||
|
otlpmetricgrpc.WithTemporalitySelector(preferDeltaTemporalitySelector),
|
||||||
|
otlpmetricgrpc.WithInsecure(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("making grpc exporter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case config.TraceClientTypeStdout:
|
||||||
|
exporter, err = stdoutmetric.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("making stdout exporter: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := metric.NewPeriodicReader(
|
||||||
|
exporter,
|
||||||
|
metric.WithInterval(params.ReadInterval),
|
||||||
|
)
|
||||||
|
provider := metric.NewMeterProvider(
|
||||||
|
metric.WithReader(reader),
|
||||||
|
metric.WithResource(r),
|
||||||
|
)
|
||||||
|
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func muxHandleFunc(router *mux.Router, name, path string, hf http.HandlerFunc) *mux.Route {
|
||||||
|
// h := otelhttp.WithRouteTag(path, hf)
|
||||||
|
return router.Handle(path, hf).Name(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func preferDeltaTemporalitySelector(kind metric.InstrumentKind) metricdata.Temporality {
|
||||||
|
switch kind {
|
||||||
|
case metric.InstrumentKindCounter,
|
||||||
|
metric.InstrumentKindObservableCounter,
|
||||||
|
metric.InstrumentKindHistogram:
|
||||||
|
return metricdata.DeltaTemporality
|
||||||
|
default:
|
||||||
|
return metricdata.CumulativeTemporality
|
||||||
|
}
|
||||||
|
}
|
||||||
69
go.mod
69
go.mod
@ -1,34 +1,65 @@
|
|||||||
module git.loyso.art/frx/kurious
|
module git.loyso.art/frx/kurious
|
||||||
|
|
||||||
go 1.21
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-resty/resty/v2 v2.10.0
|
github.com/a-h/templ v0.2.707
|
||||||
|
github.com/go-resty/resty/v2 v2.13.1
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/robfig/cron/v3 v3.0.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/teris-io/cli v1.0.1
|
github.com/teris-io/cli v1.0.1
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2
|
github.com/ydb-platform/ydb-go-sdk/v3 v3.74.3
|
||||||
github.com/ydb-platform/ydb-go-yc v0.12.1
|
github.com/ydb-platform/ydb-go-yc v0.12.1
|
||||||
golang.org/x/net v0.18.0
|
go.opentelemetry.io/otel v1.30.0
|
||||||
golang.org/x/sync v0.5.0
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.27.0
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0
|
||||||
|
go.opentelemetry.io/otel/metric v1.30.0
|
||||||
|
go.opentelemetry.io/otel/sdk v1.30.0
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.30.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.30.0
|
||||||
|
golang.org/x/net v0.29.0
|
||||||
|
golang.org/x/sync v0.8.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
|
google.golang.org/grpc v1.66.1
|
||||||
|
modernc.org/sqlite v1.30.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/a-h/templ v0.2.513 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/google/uuid v1.4.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/yandex-cloud/go-genproto v0.0.0-20240529120826-df2b24336f42 // indirect
|
||||||
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 // indirect
|
||||||
github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 // indirect
|
github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 // indirect
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect
|
golang.org/x/text v0.18.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/grpc v1.59.0 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
|
||||||
|
modernc.org/libc v1.53.3 // indirect
|
||||||
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
|
modernc.org/memory v1.8.0 // indirect
|
||||||
|
modernc.org/strutil v1.2.0 // indirect
|
||||||
|
modernc.org/token v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
183
go.sum
183
go.sum
@ -509,14 +509,16 @@ cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vf
|
|||||||
cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
|
cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
|
||||||
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
|
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||||
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
|
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/a-h/templ v0.2.513 h1:ZmwGAOx4NYllnHy+FTpusc4+c5msoMpPIYX0Oy3dNqw=
|
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||||
github.com/a-h/templ v0.2.513/go.mod h1:9gZxTLtRzM3gQxO8jr09Na0v8/jfliS97S9W5SScanM=
|
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||||
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
|
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
|
||||||
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
|
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
|
||||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
@ -527,6 +529,8 @@ github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0I
|
|||||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||||
@ -556,6 +560,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
@ -584,10 +590,17 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||||
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||||
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
|
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||||
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
@ -608,7 +621,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -627,7 +639,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
@ -648,8 +659,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
@ -670,11 +682,13 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||||
@ -697,11 +711,17 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS
|
|||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||||
@ -718,17 +738,28 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
|
||||||
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||||
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||||
@ -744,13 +775,19 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
|||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c=
|
github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c=
|
||||||
|
github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM=
|
||||||
|
github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
@ -760,6 +797,8 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
@ -769,21 +808,21 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/teris-io/cli v1.0.1 h1:J6jnVHC552uqx7zT+Ux0++tIvLmJQULqxVhCid2u/Gk=
|
github.com/teris-io/cli v1.0.1 h1:J6jnVHC552uqx7zT+Ux0++tIvLmJQULqxVhCid2u/Gk=
|
||||||
github.com/teris-io/cli v1.0.1/go.mod h1:V9nVD5aZ873RU/tQXLSXO8FieVPQhQvuNohsdsKXsGw=
|
github.com/teris-io/cli v1.0.1/go.mod h1:V9nVD5aZ873RU/tQXLSXO8FieVPQhQvuNohsdsKXsGw=
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162 h1:xCzizLC090MiLWEV3aziL5YIKrSVTRXX2DXlRGeQ6sA=
|
github.com/yandex-cloud/go-genproto v0.0.0-20240529120826-df2b24336f42 h1:l5Wu1kRcM34HqBR2FZI6tWc6QKyPziNj5fGZ4eXTCRI=
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
github.com/yandex-cloud/go-genproto v0.0.0-20240529120826-df2b24336f42/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20221215182650-986f9d10542f/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20221215182650-986f9d10542f/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20230528143953-42c825ace222/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20230528143953-42c825ace222/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd h1:dzWP1Lu+A40W883dK/Mr3xyDSM/2MggS8GtHT0qgAnE=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 h1:nL8XwD6fSst7xFUirkaWJmE7kM0CdWRYgu6+YQer1d4=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.44.0/go.mod h1:oSLwnuilwIpaF5bJJMAofnGgzPJusoI3zWMNb8I+GnM=
|
github.com/ydb-platform/ydb-go-sdk/v3 v3.44.0/go.mod h1:oSLwnuilwIpaF5bJJMAofnGgzPJusoI3zWMNb8I+GnM=
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.47.3/go.mod h1:bWnOIcUHd7+Sl7DN+yhyY1H/I61z53GczvwJgXMgvj0=
|
github.com/ydb-platform/ydb-go-sdk/v3 v3.47.3/go.mod h1:bWnOIcUHd7+Sl7DN+yhyY1H/I61z53GczvwJgXMgvj0=
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2 h1:E0yUuuX7UmPxXm92+yQCjMveLFO3zfvYFIJVuAqsVRA=
|
github.com/ydb-platform/ydb-go-sdk/v3 v3.74.3 h1:QHjqt1GA/f/O6kF5MNjmnSRG/sK1fmQivIFs1PeUgeo=
|
||||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2/go.mod h1:fjBLQ2TdQNl4bMjuWl9adoTGBypwUTPoGC+EqYqiIcU=
|
github.com/ydb-platform/ydb-go-sdk/v3 v3.74.3/go.mod h1:ZXl2InwGFiZKojWCsj6tW/SGFbch7zaehQWX5CjXMEI=
|
||||||
github.com/ydb-platform/ydb-go-yc v0.12.1 h1:qw3Fa+T81+Kpu5Io2vYHJOwcrYrVjgJlT6t/0dOXJrA=
|
github.com/ydb-platform/ydb-go-yc v0.12.1 h1:qw3Fa+T81+Kpu5Io2vYHJOwcrYrVjgJlT6t/0dOXJrA=
|
||||||
github.com/ydb-platform/ydb-go-yc v0.12.1/go.mod h1:t/ZA4ECdgPWjAb4jyDe8AzQZB5dhpGbi3iCahFaNwBY=
|
github.com/ydb-platform/ydb-go-yc v0.12.1/go.mod h1:t/ZA4ECdgPWjAb4jyDe8AzQZB5dhpGbi3iCahFaNwBY=
|
||||||
github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 h1:9E5q8Nsy2RiJMZDNVy0A3KUrIMBPakJ2VgloeWbcI84=
|
github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 h1:9E5q8Nsy2RiJMZDNVy0A3KUrIMBPakJ2VgloeWbcI84=
|
||||||
@ -805,9 +844,37 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
|
||||||
|
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.27.0 h1:/jlt1Y8gXWiHG9FBx6cJaIC5hYx5Fe64nC8w5Cylt/0=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.27.0/go.mod h1:bmToOGOBZ4hA9ghphIc1PAf66VA8KOtsuy3+ScStG20=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 h1:/0YaXu3755A/cFbtXp+21lkXgI0QE5avTWA2HjU9/WE=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0/go.mod h1:m7SFxp0/7IxmJPLIY3JhOcU9CoFzDaCPL6xxQIxhA+o=
|
||||||
|
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
|
||||||
|
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y=
|
||||||
|
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
|
||||||
|
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@ -817,7 +884,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -875,6 +943,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
|||||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -932,9 +1002,10 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||||
|
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -979,8 +1050,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -1058,9 +1129,10 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||||
|
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
@ -1069,7 +1141,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
|||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1086,15 +1159,15 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||||
|
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -1158,6 +1231,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -1361,12 +1436,10 @@ google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ
|
|||||||
google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=
|
google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=
|
||||||
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@ -1407,8 +1480,8 @@ google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsA
|
|||||||
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
|
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
|
||||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
||||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
@ -1426,10 +1499,11 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
|||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@ -1451,13 +1525,23 @@ lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl
|
|||||||
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||||
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||||
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||||
|
modernc.org/cc/v4 v4.21.3 h1:2mhBdWKtivdFlLR1ecKXTljPG1mfvbByX7QKztAIJl8=
|
||||||
|
modernc.org/cc/v4 v4.21.3/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||||
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
|
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
|
||||||
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
|
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
|
||||||
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
||||||
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
||||||
modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
|
modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
|
||||||
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
|
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
|
||||||
|
modernc.org/ccgo/v4 v4.18.1 h1:1zF5kPBFq/ZVTulBOKgQPQITdOzzyBUfC51gVYP62E4=
|
||||||
|
modernc.org/ccgo/v4 v4.18.1/go.mod h1:ao1fAxf9a2KEOL15WY8+yP3wnpaOpP/QuyFOZ9HJolM=
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
|
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||||
|
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
|
||||||
|
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||||
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||||
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
|
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
|
||||||
@ -1466,19 +1550,34 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
|||||||
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||||
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
|
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
|
||||||
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
|
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
|
||||||
|
modernc.org/libc v1.53.3 h1:9O0aSLZuHPgp49we24NoFFteRgXNLGBAQ3TODrW3XLg=
|
||||||
|
modernc.org/libc v1.53.3/go.mod h1:kb+Erju4FfHNE59xd2fNpv5CBeAeej6fHbx8p8xaiyI=
|
||||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||||
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||||
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||||
|
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
|
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
|
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||||
|
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||||
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
|
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
|
||||||
|
modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
|
||||||
|
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
|
||||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||||
|
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
|
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
0
htmlexamples/assets/style.css
Normal file
0
htmlexamples/assets/style.css
Normal file
152
htmlexamples/core.html
Normal file
152
htmlexamples/core.html
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Test page</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-bs-theme="dark">
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">Kurious</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
|
||||||
|
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="navbar-collapse collapse" id="navbarNavAltMarkup">
|
||||||
|
<div class="navbar-nav">
|
||||||
|
<a class="nav-link" aria-current="page" href="/index.html">Home</a>
|
||||||
|
<a class="nav-link" href="/courses.html">Courses</a>
|
||||||
|
<a class="nav-link active" href="/core.html">About us</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<nav style="--bs-breadcrumb-divider: '>'" aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
||||||
|
<li class="breadcrumb-item" aria-current="page">
|
||||||
|
<a href="#">Course</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Theme</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-4 g-4">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<img src="https://placehold.co/128x128" class="card-img-top" alt="" />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim
|
||||||
|
omnis vero, reiciendis obcaecati perferendis excepturi nostrum
|
||||||
|
nobis itaque modi dignissimos ...
|
||||||
|
</p>
|
||||||
|
<!-- <a href="#" class="btn btn-primary">Go somewhere</a> -->
|
||||||
|
</div>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="#" class="btn btn-primary">Buy for 399.99$</a>
|
||||||
|
<small class="text-body-secondary"></small>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end">
|
||||||
|
<small class="text-body-secondary col">399.99$</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<img src="https://placehold.co/128x128" class="card-img-top" alt="" />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim
|
||||||
|
omnis vero, reiciendis obcaecati perferendis excepturi nostrum
|
||||||
|
nobis itaque modi dignissimos ...
|
||||||
|
</p>
|
||||||
|
<a href="#" class="btn btn-primary">Go somewhere</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end">
|
||||||
|
<small class="text-body-secondary">399.99$</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<img src="https://placehold.co/128x128" class="card-img-top" alt="" />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim
|
||||||
|
omnis vero, reiciendis obcaecati perferendis excepturi nostrum
|
||||||
|
nobis itaque modi dignissimos ...
|
||||||
|
</p>
|
||||||
|
<a href="#" class="btn btn-primary">Go somewhere</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end">
|
||||||
|
<small class="text-body-secondary">399.99$</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<img src="https://placehold.co/128x128" class="card-img-top" alt="" />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim
|
||||||
|
omnis vero, reiciendis obcaecati perferendis excepturi nostrum
|
||||||
|
nobis itaque modi dignissimos ...
|
||||||
|
</p>
|
||||||
|
<a href="#" class="btn btn-primary">Go somewhere</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end">
|
||||||
|
<small class="text-body-secondary">399.99$</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<img src="https://placehold.co/128x128" class="card-img-top" alt="" />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim
|
||||||
|
omnis vero, reiciendis obcaecati perferendis excepturi nostrum
|
||||||
|
nobis itaque modi dignissimos ...
|
||||||
|
</p>
|
||||||
|
<a href="#" class="btn btn-primary">Go somewhere</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end">
|
||||||
|
<small class="text-body-secondary">399.99$</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
277
htmlexamples/courses.html
Normal file
277
htmlexamples/courses.html
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Test page</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<link rel="stylesheet" href="/assets/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-bs-theme="dark">
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary w-auto">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/index.html">Kurious</a>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent"
|
||||||
|
aria-controls="navbarSupportedContent"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="navbar-collapse collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav mb-lg-0 mb-2 me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="/index.html"
|
||||||
|
>Home</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
|
class="nav-link active"
|
||||||
|
aria-current="page"
|
||||||
|
href="/courses.html"
|
||||||
|
>Courses</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/core.html">About us</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<section class="row header">
|
||||||
|
<nav
|
||||||
|
class="mt-4"
|
||||||
|
style="--bs-breadcrumb-divider: ">""
|
||||||
|
aria-label="breadcrumb"
|
||||||
|
>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="#">Main</a></li>
|
||||||
|
<li class="breadcrumb-item" aria-current="page">
|
||||||
|
<a href="#">Languages</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Japanese</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="row filters">
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Filter categories</span>
|
||||||
|
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="inputGroupSelect04"
|
||||||
|
aria-label="Example select with button addon"
|
||||||
|
>
|
||||||
|
<option selected>All</option>
|
||||||
|
<option value="1">Programming</option>
|
||||||
|
<option value="2">Design</option>
|
||||||
|
<option value="3">Business</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="inputGroupSelect04"
|
||||||
|
aria-label="Example select with button addon"
|
||||||
|
>
|
||||||
|
<option selected>All</option>
|
||||||
|
<option value="1">Web development</option>
|
||||||
|
<option value="2">Backend</option>
|
||||||
|
<option value="3">Frontend</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-secondary" type="button">
|
||||||
|
> Go
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="filter-content d-flex mb-3">
|
||||||
|
<!-- School list -->
|
||||||
|
<div class="p-2 col-auto">
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="inputGroupSelect04"
|
||||||
|
aria-label="Example select with button addon"
|
||||||
|
>
|
||||||
|
<option selected>Pick a school</option>
|
||||||
|
<option value="1">First school in the row</option>
|
||||||
|
<option value="2">Second but not the shortest named school</option>
|
||||||
|
<option value="3">Third small</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<!-- Sort option -->
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="input-group flex-nowrap p-2">
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="inputGroupSelect04"
|
||||||
|
aria-label="Example select with button addon"
|
||||||
|
>
|
||||||
|
<option selected>Sort by</option>
|
||||||
|
<option value="1">One</option>
|
||||||
|
<option value="2">Two</option>
|
||||||
|
<option value="3">Three</option>
|
||||||
|
<option value="4">Threerrrrrrrrrrrrrrrrrr</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check p-2"
|
||||||
|
name="options-base"
|
||||||
|
id="option6"
|
||||||
|
autocomplete="off"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label class="btn" for="option6">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-sort-down"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3.5 2.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 11.293zm3.5 1a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M7.5 6a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check p-2"
|
||||||
|
name="options-base"
|
||||||
|
id="option5"
|
||||||
|
autocomplete="off"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label class="btn" for="option5">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-sort-up"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3.5 12.5a.5.5 0 0 1-1 0V3.707L1.354 4.854a.5.5 0 1 1-.708-.708l2-1.999.007-.007a.5.5 0 0 1 .7.006l2 2a.5.5 0 1 1-.707.708L3.5 3.707zm3.5-9a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M7.5 6a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Spacer -->
|
||||||
|
<div class="col-4"></div>
|
||||||
|
<!-- Promocodes button -->
|
||||||
|
<div class="col-auto ms-auto p-2">
|
||||||
|
<div class="btn btn-primary">Promocodes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="card">
|
||||||
|
<img
|
||||||
|
src="https://placehold.co/128x128"
|
||||||
|
class="card-img-top"
|
||||||
|
alt="..."
|
||||||
|
/>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Card title with a long naming</h5>
|
||||||
|
<div class="input-group d-flex">
|
||||||
|
<a href="#" class="btn text btn-outline-primary flex-grow-1"
|
||||||
|
>Open ></a
|
||||||
|
>
|
||||||
|
<span class="input-group-text justify-content-end flex-fill"
|
||||||
|
>500$</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Maybe add spacing in a better way? -->
|
||||||
|
<p class="p-2"></p>
|
||||||
|
<footer class="text-center text-lg-start bg-body-tertiary text-muted">
|
||||||
|
<section class="p-2">
|
||||||
|
<div class="container text-center text-md-start mt-4">
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-3 col-lg-4 col-xl-3 mx-auto mb-4">
|
||||||
|
<h6 class="text-uppercase fw-bold mb-4">
|
||||||
|
<i class="fas fa-gem me-3"></i>Courses
|
||||||
|
</h6>
|
||||||
|
<p>
|
||||||
|
Welcome to Courses, your gateway to learning! Explore a diverse
|
||||||
|
range of courses and advance your skills with us. Join our
|
||||||
|
community and transform your life through education.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3 col-lg-2 col-xl-2 mx-auto mb-4">
|
||||||
|
<h6 class="text-uppercase fw-bold mb-4">Useful links</h6>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Pricing</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Settings</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Orders</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Help</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4 col-lg-3 col-xl-3 mx-auto mb-md-0 mb-4">
|
||||||
|
<h6 class="text-uppercase fw-bold mb-4">Contact</h6>
|
||||||
|
<p><i class="fas fa-home me-3"></i> New York, NY 10012, US</p>
|
||||||
|
<p>
|
||||||
|
<i class="fas fa-envelope me-3"></i>
|
||||||
|
info@example.com
|
||||||
|
</p>
|
||||||
|
<p><i class="fas fa-phone me-3"></i> + 01 234 567 88</p>
|
||||||
|
<p><i class="fas fa-print me-3"></i> + 01 234 567 89</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="text-center p-4"
|
||||||
|
style="background-color: rgba(0, 0, 0, 0.05)"
|
||||||
|
>
|
||||||
|
© 2024 Copyright:
|
||||||
|
<a class="text-reset fw-bold" href="https://mdbootstrap.com/"
|
||||||
|
>kursov.net</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
151
htmlexamples/index.html
Normal file
151
htmlexamples/index.html
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Test page</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="/assets/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-bs-theme="dark" style="margin: 0">
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary w-auto">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/index.html">Kurious</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
|
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="navbar-collapse collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav mb-lg-0 mb-2 me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="/index.html">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="/courses.html">Courses</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/core.html">About us</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row upper mb-4 text-center" style="min-height: 4rem">
|
||||||
|
<p class="justify-content-center">Here you can find course for any taste</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container w-75 mb-4">
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
|
||||||
|
<div class="col-12 col-md-8 col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Programming</h5>
|
||||||
|
<hr/>
|
||||||
|
<p>In this category you can find courses of types such as</p>
|
||||||
|
<p>web-development, backend development, frontend developent</p>
|
||||||
|
<ul>
|
||||||
|
<li><span class="d-inline-block text-truncate col-8">web-development</span></li>
|
||||||
|
<li>backend development</li>
|
||||||
|
<li>frontend development</li>
|
||||||
|
</ul>
|
||||||
|
<p>This category contains <span>128</span> courses.</p>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="btn btn-sm btn-outline-primary col-6">
|
||||||
|
Open
|
||||||
|
</a>
|
||||||
|
<small class="text-body-secondary">128 items</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Programming</h5>
|
||||||
|
<hr/>
|
||||||
|
<p>In this category you can find courses of types such as</p>
|
||||||
|
<p>web-development, backend development, frontend developent</p>
|
||||||
|
<p>This category contains <span>128</span> courses.</p>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="btn btn-sm btn-outline-primary col-6">
|
||||||
|
Open
|
||||||
|
</a>
|
||||||
|
<small class="text-body-secondary">128 items</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Programming</h5>
|
||||||
|
<hr/>
|
||||||
|
<p>In this category you can find courses of types such as</p>
|
||||||
|
<p>web-development, backend development, frontend developent</p>
|
||||||
|
<p>This category contains <span>128</span> courses.</p>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="btn btn-sm btn-outline-primary col-6">
|
||||||
|
Open
|
||||||
|
</a>
|
||||||
|
<small class="text-body-secondary">128 items</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Programming</h5>
|
||||||
|
<hr/>
|
||||||
|
<p>In this category you can find courses of types such as</p>
|
||||||
|
<p>web-development, backend development, frontend developent</p>
|
||||||
|
<p>This category contains <span>128</span> courses.</p>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="btn btn-sm btn-outline-primary col-6">
|
||||||
|
Open
|
||||||
|
</a>
|
||||||
|
<small class="text-body-secondary">128 items</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav aria-label="Page navigation example">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link">Previous</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="#">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -31,7 +31,7 @@ type Client interface {
|
|||||||
ListEducationalProducts(
|
ListEducationalProducts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
params ListEducationProductsParams,
|
params ListEducationProductsParams,
|
||||||
) (result listEducationProductsResponse, err error)
|
) (result ListEducationProductsResponse, err error)
|
||||||
ListEducationalProductsFilterCount(
|
ListEducationalProductsFilterCount(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
params ListEducationProductsParams,
|
params ListEducationProductsParams,
|
||||||
@ -177,7 +177,7 @@ type listEducationProductsRequest struct {
|
|||||||
SortDirection string `json:"sortDirection"`
|
SortDirection string `json:"sortDirection"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type listEducationProductsResponse struct {
|
type ListEducationProductsResponse struct {
|
||||||
Items []Course `json:"items"`
|
Items []Course `json:"items"`
|
||||||
Organizations map[string]Organization `json:"organizations"`
|
Organizations map[string]Organization `json:"organizations"`
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ type listEducationProductsResponse struct {
|
|||||||
func (c *client) ListEducationalProducts(
|
func (c *client) ListEducationalProducts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
params ListEducationProductsParams,
|
params ListEducationProductsParams,
|
||||||
) (result listEducationProductsResponse, err error) {
|
) (result ListEducationProductsResponse, err error) {
|
||||||
const urlPath = "/v1/education/products"
|
const urlPath = "/v1/education/products"
|
||||||
const defaultLimit = 1
|
const defaultLimit = 1
|
||||||
const defaultSortProp = "advertising.position"
|
const defaultSortProp = "advertising.position"
|
||||||
|
|||||||
208
internal/common/client/sravni/mocks/Client.go
Normal file
208
internal/common/client/sravni/mocks/Client.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
sravni "git.loyso.art/frx/kurious/internal/common/client/sravni"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is an autogenerated mock type for the Client type
|
||||||
|
type Client struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *Client) EXPECT() *Client_Expecter {
|
||||||
|
return &Client_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMainPageState provides a mock function with given fields:
|
||||||
|
func (_m *Client) GetMainPageState() (*sravni.PageState, error) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for GetMainPageState")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 *sravni.PageState
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (*sravni.PageState, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func() *sravni.PageState); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*sravni.PageState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client_GetMainPageState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMainPageState'
|
||||||
|
type Client_GetMainPageState_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMainPageState is a helper method to define mock.On call
|
||||||
|
func (_e *Client_Expecter) GetMainPageState() *Client_GetMainPageState_Call {
|
||||||
|
return &Client_GetMainPageState_Call{Call: _e.mock.On("GetMainPageState")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_GetMainPageState_Call) Run(run func()) *Client_GetMainPageState_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_GetMainPageState_Call) Return(_a0 *sravni.PageState, _a1 error) *Client_GetMainPageState_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_GetMainPageState_Call) RunAndReturn(run func() (*sravni.PageState, error)) *Client_GetMainPageState_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEducationalProducts provides a mock function with given fields: ctx, params
|
||||||
|
func (_m *Client) ListEducationalProducts(ctx context.Context, params sravni.ListEducationProductsParams) (sravni.ListEducationProductsResponse, error) {
|
||||||
|
ret := _m.Called(ctx, params)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ListEducationalProducts")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 sravni.ListEducationProductsResponse
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) (sravni.ListEducationProductsResponse, error)); ok {
|
||||||
|
return rf(ctx, params)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) sravni.ListEducationProductsResponse); ok {
|
||||||
|
r0 = rf(ctx, params)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(sravni.ListEducationProductsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, sravni.ListEducationProductsParams) error); ok {
|
||||||
|
r1 = rf(ctx, params)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client_ListEducationalProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEducationalProducts'
|
||||||
|
type Client_ListEducationalProducts_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEducationalProducts is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - params sravni.ListEducationProductsParams
|
||||||
|
func (_e *Client_Expecter) ListEducationalProducts(ctx interface{}, params interface{}) *Client_ListEducationalProducts_Call {
|
||||||
|
return &Client_ListEducationalProducts_Call{Call: _e.mock.On("ListEducationalProducts", ctx, params)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_ListEducationalProducts_Call) Run(run func(ctx context.Context, params sravni.ListEducationProductsParams)) *Client_ListEducationalProducts_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(sravni.ListEducationProductsParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_ListEducationalProducts_Call) Return(result sravni.ListEducationProductsResponse, err error) *Client_ListEducationalProducts_Call {
|
||||||
|
_c.Call.Return(result, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_ListEducationalProducts_Call) RunAndReturn(run func(context.Context, sravni.ListEducationProductsParams) (sravni.ListEducationProductsResponse, error)) *Client_ListEducationalProducts_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEducationalProductsFilterCount provides a mock function with given fields: ctx, params
|
||||||
|
func (_m *Client) ListEducationalProductsFilterCount(ctx context.Context, params sravni.ListEducationProductsParams) (sravni.ProductsFilterCount, error) {
|
||||||
|
ret := _m.Called(ctx, params)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ListEducationalProductsFilterCount")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 sravni.ProductsFilterCount
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) (sravni.ProductsFilterCount, error)); ok {
|
||||||
|
return rf(ctx, params)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) sravni.ProductsFilterCount); ok {
|
||||||
|
r0 = rf(ctx, params)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(sravni.ProductsFilterCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, sravni.ListEducationProductsParams) error); ok {
|
||||||
|
r1 = rf(ctx, params)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client_ListEducationalProductsFilterCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEducationalProductsFilterCount'
|
||||||
|
type Client_ListEducationalProductsFilterCount_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEducationalProductsFilterCount is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - params sravni.ListEducationProductsParams
|
||||||
|
func (_e *Client_Expecter) ListEducationalProductsFilterCount(ctx interface{}, params interface{}) *Client_ListEducationalProductsFilterCount_Call {
|
||||||
|
return &Client_ListEducationalProductsFilterCount_Call{Call: _e.mock.On("ListEducationalProductsFilterCount", ctx, params)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_ListEducationalProductsFilterCount_Call) Run(run func(ctx context.Context, params sravni.ListEducationProductsParams)) *Client_ListEducationalProductsFilterCount_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(sravni.ListEducationProductsParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_ListEducationalProductsFilterCount_Call) Return(result sravni.ProductsFilterCount, err error) *Client_ListEducationalProductsFilterCount_Call {
|
||||||
|
_c.Call.Return(result, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Client_ListEducationalProductsFilterCount_Call) RunAndReturn(run func(context.Context, sravni.ListEducationProductsParams) (sravni.ProductsFilterCount, error)) *Client_ListEducationalProductsFilterCount_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewClient(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *Client {
|
||||||
|
mock := &Client{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
@ -12,6 +12,6 @@ func (NoopClient) GetMainPageState() (*PageState, error) {
|
|||||||
return nil, errors.ErrNotImplemented
|
return nil, errors.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (listEducationProductsResponse, error) {
|
func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (ListEducationProductsResponse, error) {
|
||||||
return listEducationProductsResponse{}, errors.ErrNotImplemented
|
return ListEducationProductsResponse{}, errors.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,4 @@ package config
|
|||||||
type HTTP struct {
|
type HTTP struct {
|
||||||
ListenAddr string `json:"listen_addr"`
|
ListenAddr string `json:"listen_addr"`
|
||||||
MountLive bool `json:"mount_live"`
|
MountLive bool `json:"mount_live"`
|
||||||
Engine string `json:"engine"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
internal/common/config/sqlite.go
Normal file
8
internal/common/config/sqlite.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Sqlite struct {
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
ShutdownTimeout time.Duration `json:"shutdown_timeout"`
|
||||||
|
}
|
||||||
38
internal/common/config/trace.go
Normal file
38
internal/common/config/trace.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type TraceClientType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TraceClientTypeUnset TraceClientType = iota
|
||||||
|
TraceClientTypeHTTP
|
||||||
|
TraceClientTypeGRPC
|
||||||
|
TraceClientTypeStdout
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *TraceClientType) UnmarshalText(data []byte) error {
|
||||||
|
dataStr := string(data)
|
||||||
|
switch dataStr {
|
||||||
|
case "http":
|
||||||
|
*t = TraceClientTypeHTTP
|
||||||
|
case "grpc":
|
||||||
|
*t = TraceClientTypeGRPC
|
||||||
|
case "stdout":
|
||||||
|
*t = TraceClientTypeStdout
|
||||||
|
case "":
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported value " + dataStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Trace struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
APIKey string `json:"api_key"`
|
||||||
|
APIHeader string `json:"api_header"`
|
||||||
|
Type TraceClientType `json:"type"`
|
||||||
|
|
||||||
|
ShowMetrics bool `json:"show_metrics"`
|
||||||
|
}
|
||||||
@ -2,11 +2,24 @@ package decorator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nameAttribute = attribute.Key("cq.name")
|
||||||
|
argsAttribute = attribute.Key("cq.args")
|
||||||
|
|
||||||
|
apiTracer = otel.Tracer("api")
|
||||||
)
|
)
|
||||||
|
|
||||||
type commandLoggingDecorator[T any] struct {
|
type commandLoggingDecorator[T any] struct {
|
||||||
@ -18,6 +31,17 @@ func (c commandLoggingDecorator[T]) Handle(ctx context.Context, cmd T) (err erro
|
|||||||
handlerName := getTypeName[T]()
|
handlerName := getTypeName[T]()
|
||||||
|
|
||||||
ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName))
|
ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName))
|
||||||
|
|
||||||
|
var argsBuilder strings.Builder
|
||||||
|
_ = json.NewEncoder(&argsBuilder).Encode(cmd)
|
||||||
|
|
||||||
|
var span trace.Span
|
||||||
|
ctx, span = apiTracer.Start(ctx, "command "+handlerName)
|
||||||
|
span.SetAttributes(
|
||||||
|
nameAttribute.String(handlerName),
|
||||||
|
argsAttribute.String(argsBuilder.String()),
|
||||||
|
)
|
||||||
|
|
||||||
xcontext.LogDebug(ctx, c.log, "executing command")
|
xcontext.LogDebug(ctx, c.log, "executing command")
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
@ -27,7 +51,9 @@ func (c commandLoggingDecorator[T]) Handle(ctx context.Context, cmd T) (err erro
|
|||||||
xcontext.LogInfo(ctx, c.log, "command executed successfuly", elapsed)
|
xcontext.LogInfo(ctx, c.log, "command executed successfuly", elapsed)
|
||||||
} else {
|
} else {
|
||||||
xcontext.LogError(ctx, c.log, "command execution failed", elapsed, slog.Any("err", err))
|
xcontext.LogError(ctx, c.log, "command execution failed", elapsed, slog.Any("err", err))
|
||||||
|
span.RecordError(err)
|
||||||
}
|
}
|
||||||
|
span.End()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return c.base.Handle(ctx, cmd)
|
return c.base.Handle(ctx, cmd)
|
||||||
@ -41,6 +67,17 @@ type queryLoggingDecorator[Q, U any] struct {
|
|||||||
func (q queryLoggingDecorator[Q, U]) Handle(ctx context.Context, query Q) (entity U, err error) {
|
func (q queryLoggingDecorator[Q, U]) Handle(ctx context.Context, query Q) (entity U, err error) {
|
||||||
handlerName := getTypeName[Q]()
|
handlerName := getTypeName[Q]()
|
||||||
ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName))
|
ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName))
|
||||||
|
|
||||||
|
var argsBuilder strings.Builder
|
||||||
|
_ = json.NewEncoder(&argsBuilder).Encode(query)
|
||||||
|
|
||||||
|
var span trace.Span
|
||||||
|
ctx, span = apiTracer.Start(ctx, "query "+handlerName)
|
||||||
|
span.SetAttributes(
|
||||||
|
nameAttribute.String(handlerName),
|
||||||
|
argsAttribute.String(argsBuilder.String()),
|
||||||
|
)
|
||||||
|
|
||||||
xcontext.LogDebug(ctx, q.log, "executing command")
|
xcontext.LogDebug(ctx, q.log, "executing command")
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
@ -50,7 +87,10 @@ func (q queryLoggingDecorator[Q, U]) Handle(ctx context.Context, query Q) (entit
|
|||||||
xcontext.LogInfo(ctx, q.log, "command executed successfuly", elapsed)
|
xcontext.LogInfo(ctx, q.log, "command executed successfuly", elapsed)
|
||||||
} else {
|
} else {
|
||||||
xcontext.LogError(ctx, q.log, "command execution failed", elapsed, slog.Any("err", err))
|
xcontext.LogError(ctx, q.log, "command execution failed", elapsed, slog.Any("err", err))
|
||||||
|
span.RecordError(err)
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
|
span.End(trace.WithTimestamp(now))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return q.base.Handle(ctx, query)
|
return q.base.Handle(ctx, query)
|
||||||
|
|||||||
@ -6,6 +6,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ctxLogKey struct{}
|
type ctxLogKey struct{}
|
||||||
|
type ctxRequestID struct{}
|
||||||
|
|
||||||
|
func WithRequestID(ctx context.Context, requestID string) context.Context {
|
||||||
|
return context.WithValue(ctx, ctxRequestID{}, requestID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRequestID(ctx context.Context) string {
|
||||||
|
reqid, _ := ctx.Value(ctxRequestID{}).(string)
|
||||||
|
return reqid
|
||||||
|
}
|
||||||
|
|
||||||
type ctxLogAttrStore struct {
|
type ctxLogAttrStore struct {
|
||||||
attrs []slog.Attr
|
attrs []slog.Attr
|
||||||
|
|||||||
@ -1,7 +1,29 @@
|
|||||||
package xslices
|
package xslices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
func ForEach[T any](items []T, f func(T)) {
|
func ForEach[T any](items []T, f func(T)) {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
f(item)
|
f(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AsMap[T any, U comparable](items []T, f func(T) U) map[U]struct{} {
|
||||||
|
out := make(map[U]struct{}, len(items))
|
||||||
|
ForEach(items, func(in T) {
|
||||||
|
out[f(in)] = struct{}{}
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Shuffle[T any](items []T) {
|
||||||
|
maxnum := big.NewInt(int64(len(items)))
|
||||||
|
for i := range items {
|
||||||
|
swapWith, _ := rand.Int(rand.Reader, maxnum)
|
||||||
|
swapWithIdx := int(swapWith.Int64())
|
||||||
|
items[i], items[swapWithIdx] = items[swapWithIdx], items[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
103
internal/common/xslices/lru.go
Normal file
103
internal/common/xslices/lru.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package xslices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLRU[K comparable, T any](capacity int) *lru[K, T] {
|
||||||
|
return &lru[K, T]{
|
||||||
|
items: make(map[K]*lruNode[K, T], capacity),
|
||||||
|
capacity: capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type lruNode[K comparable, T any] struct {
|
||||||
|
key K
|
||||||
|
value T
|
||||||
|
|
||||||
|
next *lruNode[K, T]
|
||||||
|
prev *lruNode[K, T]
|
||||||
|
}
|
||||||
|
|
||||||
|
type lru[K comparable, T any] struct {
|
||||||
|
items map[K]*lruNode[K, T]
|
||||||
|
length int
|
||||||
|
capacity int
|
||||||
|
|
||||||
|
first *lruNode[K, T]
|
||||||
|
last *lruNode[K, T]
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lru[K, T]) Push(key K, value T) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
node, ok := l.items[key]
|
||||||
|
if ok {
|
||||||
|
l.bumpUnsafe(node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node = &lruNode[K, T]{
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
next: l.first,
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.first != nil {
|
||||||
|
l.first.prev = node
|
||||||
|
}
|
||||||
|
if l.last == nil {
|
||||||
|
l.last = node
|
||||||
|
}
|
||||||
|
|
||||||
|
l.first = node
|
||||||
|
l.items[key] = node
|
||||||
|
|
||||||
|
if l.length == l.capacity && l.last != nil {
|
||||||
|
deletedNode := l.last
|
||||||
|
delete(l.items, deletedNode.key)
|
||||||
|
|
||||||
|
l.last = l.last.prev
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.length++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lru[K, T]) Get(key K) (T, bool) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
node, ok := l.items[key]
|
||||||
|
if !ok {
|
||||||
|
var t T
|
||||||
|
return t, false
|
||||||
|
}
|
||||||
|
|
||||||
|
out := node.value
|
||||||
|
l.bumpUnsafe(node)
|
||||||
|
|
||||||
|
return out, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lru[K, T]) bumpUnsafe(node *lruNode[K, T]) {
|
||||||
|
if l.first == node {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.next != nil {
|
||||||
|
node.next.prev = node.prev
|
||||||
|
}
|
||||||
|
if node.prev != nil {
|
||||||
|
node.prev.next = node.next
|
||||||
|
}
|
||||||
|
|
||||||
|
node.next = l.first
|
||||||
|
l.first.prev = node
|
||||||
|
|
||||||
|
l.first = node
|
||||||
|
node.prev = nil
|
||||||
|
}
|
||||||
23
internal/common/xslices/lru_test.go
Normal file
23
internal/common/xslices/lru_test.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package xslices
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLRU(t *testing.T) {
|
||||||
|
lru := NewLRU[int, string](3)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
lru.Push(i, "v")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
_, found := lru.Get(i)
|
||||||
|
if i == 0 {
|
||||||
|
if found {
|
||||||
|
t.Error("expected value to be flushed out of cache")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("expected value %d to be found", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
internal/kurious/adapters/adapters.go
Normal file
40
internal/kurious/adapters/adapters.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dbTracer = otel.Tracer("adapters.db")
|
||||||
|
|
||||||
|
var (
|
||||||
|
dbSystemAttr = attribute.Key("db.system")
|
||||||
|
dbNameAttr = attribute.Key("db.name")
|
||||||
|
dbStatementAttr = attribute.Key("db.statement")
|
||||||
|
dbOperationAttr = attribute.Key("db.operation")
|
||||||
|
dbTableAttr = attribute.Key("db.sql.table")
|
||||||
|
dbArgumentsAttr = attribute.Key("db.arguments")
|
||||||
|
)
|
||||||
|
|
||||||
|
type domainer[T any] interface {
|
||||||
|
AsDomain() T
|
||||||
|
}
|
||||||
|
|
||||||
|
func asDomainFunc[T any, U domainer[T]](in U) (out T) {
|
||||||
|
return in.AsDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinColumns(columns []string) string {
|
||||||
|
return strings.Join(columns, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func namedArgColumns(columns []string) string {
|
||||||
|
out := make([]string, len(columns))
|
||||||
|
for i, col := range columns {
|
||||||
|
out[i] = ":" + col
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinColumns(out)
|
||||||
|
}
|
||||||
@ -1,21 +1,111 @@
|
|||||||
package adapters
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xslices"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
type inMemoryMapper struct {
|
type inMemoryMapper struct {
|
||||||
courseThematicsByID map[string]string
|
courseThematicsByID map[string]string
|
||||||
learningTypeByID map[string]string
|
learningTypeByID map[string]string
|
||||||
|
|
||||||
|
stats map[string]domain.LearningTypeStat
|
||||||
|
courseThematicByLearningType map[string]string
|
||||||
|
totalCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryMapper(courseThematics, learningType map[string]string) inMemoryMapper {
|
func NewMemoryMapper(courseThematics, learningType map[string]string) *inMemoryMapper {
|
||||||
return inMemoryMapper{
|
return &inMemoryMapper{
|
||||||
courseThematicsByID: courseThematics,
|
courseThematicsByID: courseThematics,
|
||||||
learningTypeByID: learningType,
|
learningTypeByID: learningType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m inMemoryMapper) CourseThematicNameByID(id string) string {
|
func (m *inMemoryMapper) CollectCounts(ctx context.Context, cr domain.CourseRepository) error {
|
||||||
|
const batchSize = 1000
|
||||||
|
|
||||||
|
m.stats = map[string]domain.LearningTypeStat{}
|
||||||
|
m.courseThematicByLearningType = map[string]string{}
|
||||||
|
|
||||||
|
var offset int
|
||||||
|
for {
|
||||||
|
result, err := cr.List(ctx, domain.ListCoursesParams{
|
||||||
|
LearningType: "",
|
||||||
|
CourseThematic: "",
|
||||||
|
Limit: batchSize + 1,
|
||||||
|
Offset: offset,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing courses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
courses := result.Courses
|
||||||
|
if len(result.Courses) >= batchSize {
|
||||||
|
courses = result.Courses[:batchSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
m.totalCount += len(courses)
|
||||||
|
|
||||||
|
xslices.ForEach(courses, func(course domain.Course) {
|
||||||
|
stat, ok := m.stats[course.LearningTypeID]
|
||||||
|
stat.Count++
|
||||||
|
if !ok {
|
||||||
|
stat.CourseThematic = map[string]int{}
|
||||||
|
}
|
||||||
|
stat.CourseThematic[course.ThematicID]++
|
||||||
|
m.stats[course.LearningTypeID] = stat
|
||||||
|
m.courseThematicByLearningType[course.ThematicID] = course.LearningTypeID
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(result.Courses) != batchSize+1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += batchSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *inMemoryMapper) GetCounts(byCourseThematic, byLearningType string) int {
|
||||||
|
if byCourseThematic != "" && byLearningType == "" {
|
||||||
|
byLearningType = m.courseThematicByLearningType[byCourseThematic]
|
||||||
|
}
|
||||||
|
if byLearningType != "" {
|
||||||
|
stat := m.stats[byLearningType]
|
||||||
|
if byCourseThematic != "" {
|
||||||
|
return stat.CourseThematic[byCourseThematic]
|
||||||
|
}
|
||||||
|
return stat.Count
|
||||||
|
}
|
||||||
|
return m.totalCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *inMemoryMapper) GetStats(copyMap bool) map[string]domain.LearningTypeStat {
|
||||||
|
if !copyMap {
|
||||||
|
return m.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]domain.LearningTypeStat, len(m.stats))
|
||||||
|
for learningType, stats := range m.stats {
|
||||||
|
copiedStats := make(map[string]int, len(stats.CourseThematic))
|
||||||
|
for courseThematic, count := range stats.CourseThematic {
|
||||||
|
copiedStats[courseThematic] = count
|
||||||
|
}
|
||||||
|
out[learningType] = domain.LearningTypeStat{
|
||||||
|
Count: stats.Count,
|
||||||
|
CourseThematic: copiedStats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *inMemoryMapper) CourseThematicNameByID(id string) string {
|
||||||
return m.courseThematicsByID[id]
|
return m.courseThematicsByID[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m inMemoryMapper) LearningTypeNameByID(id string) string {
|
func (m *inMemoryMapper) LearningTypeNameByID(id string) string {
|
||||||
return m.learningTypeByID[id]
|
return m.learningTypeByID[id]
|
||||||
}
|
}
|
||||||
|
|||||||
60
internal/kurious/adapters/sqlite_basetest.go
Normal file
60
internal/kurious/adapters/sqlite_basetest.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/config"
|
||||||
|
"git.loyso.art/frx/kurious/migrations/sqlite"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteBaseSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
// TODO: make baseTestSuite that provides this kind of things
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
log *slog.Logger
|
||||||
|
|
||||||
|
connection *sqliteConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteBaseSuite) SetupSuite() {
|
||||||
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
||||||
|
s.log = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
|
AddSource: false,
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
}))
|
||||||
|
|
||||||
|
connection, err := NewSqliteConnection(s.ctx, config.Sqlite{
|
||||||
|
DSN: ":memory:",
|
||||||
|
ShutdownTimeout: time.Second * 3,
|
||||||
|
}, s.log)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.connection = connection
|
||||||
|
db := s.connection.db
|
||||||
|
|
||||||
|
err = sqlite.RunMigrations(s.ctx, db.DB, s.log.With(slog.String("component", "migrator")))
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteBaseSuite) TearDownSuite() {
|
||||||
|
s.cancel()
|
||||||
|
err := s.connection.Close()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteBaseSuite) TearDownTest() {
|
||||||
|
db := s.connection.db
|
||||||
|
for _, query := range []string{
|
||||||
|
"DELETE FROM learning_categories",
|
||||||
|
"DELETE FROM courses",
|
||||||
|
} {
|
||||||
|
_, err := db.ExecContext(s.ctx, query)
|
||||||
|
s.Require().NoError(err, "cleaning up database")
|
||||||
|
}
|
||||||
|
}
|
||||||
54
internal/kurious/adapters/sqlite_connection.go
Normal file
54
internal/kurious/adapters/sqlite_connection.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/config"
|
||||||
|
"git.loyso.art/frx/kurious/migrations/sqlite"
|
||||||
|
"git.loyso.art/frx/kurious/pkg/xdefault"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqliteConnection struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
shutdownTimeout time.Duration
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSqliteConnection(ctx context.Context, cfg config.Sqlite, log *slog.Logger) (*sqliteConnection, error) {
|
||||||
|
conn, err := sqlx.Open("sqlite", cfg.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("openning db connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sqlite.RunMigrations(ctx, conn.DB, log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("running migrations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sqliteConnection{
|
||||||
|
db: conn,
|
||||||
|
log: log,
|
||||||
|
shutdownTimeout: xdefault.WithFallback(cfg.ShutdownTimeout, defaultShutdownTimeout),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConnection) Close() error {
|
||||||
|
_, cancel := context.WithTimeout(context.Background(), c.shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return c.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSqliteBaseAttributes() []attribute.KeyValue {
|
||||||
|
return []attribute.KeyValue{
|
||||||
|
dbSystemAttr.String("sqlite"),
|
||||||
|
dbNameAttr.String("courses"),
|
||||||
|
}
|
||||||
|
}
|
||||||
565
internal/kurious/adapters/sqlite_course_repository.go
Normal file
565
internal/kurious/adapters/sqlite_course_repository.go
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xslices"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *sqliteConnection) CourseRepository() *sqliteCourseRepository {
|
||||||
|
return &sqliteCourseRepository{
|
||||||
|
db: c.db,
|
||||||
|
log: c.log.With(slog.String("repository", "course")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteCourseRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) List(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListCoursesParams,
|
||||||
|
) (result domain.ListCoursesResult, err error) {
|
||||||
|
const queryTemplate = `SELECT %s from courses WHERE 1=1`
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list courses.courses",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
query := fmt.Sprintf(queryTemplate, coursesFieldsStr)
|
||||||
|
args := make([]any, 0, 6)
|
||||||
|
if params.LearningType != "" {
|
||||||
|
args = append(args, params.LearningType)
|
||||||
|
query += " AND learning_type = ?"
|
||||||
|
}
|
||||||
|
if params.CourseThematic != "" {
|
||||||
|
args = append(args, params.CourseThematic)
|
||||||
|
query += " AND course_thematic = ?"
|
||||||
|
}
|
||||||
|
if params.OrganizationID != "" {
|
||||||
|
args = append(args, params.OrganizationID)
|
||||||
|
query += " AND organization_id = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.OrderBy == "" {
|
||||||
|
params.OrderBy = "id"
|
||||||
|
}
|
||||||
|
|
||||||
|
direction := "ASC"
|
||||||
|
if !params.Ascending {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query += " ORDER BY " + params.OrderBy + " " + direction
|
||||||
|
|
||||||
|
if params.Limit > 0 {
|
||||||
|
query += " LIMIT ?"
|
||||||
|
args = append(args, params.Limit)
|
||||||
|
}
|
||||||
|
if params.Offset > 0 {
|
||||||
|
query += " OFFSET ?"
|
||||||
|
args = append(args, params.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(dbStatementAttr.String(query))
|
||||||
|
|
||||||
|
scanF := func(s rowsScanner) (err error) {
|
||||||
|
var cdb sqliteCourseDB
|
||||||
|
err = s.StructScan(&cdb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Courses = append(result.Courses, cdb.AsDomain())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = scanRows(ctx, r.db, scanF, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Limit > 0 && len(result.Courses) == params.Limit {
|
||||||
|
lastIDx := len(result.Courses) - 1
|
||||||
|
result.NextPageToken = result.Courses[lastIDx].ID
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Count, err = r.listCount(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogWithWarnError(ctx, r.log, err, "unable to list count")
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int("db.items_count", len(result.Courses)),
|
||||||
|
attribute.Int("db.total_items", result.Count),
|
||||||
|
)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) ListLearningTypes(
|
||||||
|
ctx context.Context,
|
||||||
|
) (result domain.ListLearningTypeResult, err error) {
|
||||||
|
const query = "SELECT DISTINCT learning_type FROM courses"
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list_learning_types courses.courses",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = r.db.SelectContext(ctx, &result.LearningTypeIDs, query)
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) ListCourseThematics(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListCourseThematicsParams,
|
||||||
|
) (result domain.ListCourseThematicsResult, err error) {
|
||||||
|
const queryTemplate = "SELECT DISTINCT course_thematic FROM courses WHERE 1=1"
|
||||||
|
|
||||||
|
query := queryTemplate
|
||||||
|
args := make([]any, 0, 1)
|
||||||
|
if params.LearningTypeID != "" {
|
||||||
|
args = append(args, params.LearningTypeID)
|
||||||
|
query += " AND learning_type = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list courses.course_thematic",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = r.db.SelectContext(ctx, &result.CourseThematicIDs, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) ListStatistics(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListStatisticsParams,
|
||||||
|
) (result domain.ListStatisticsResult, err error) {
|
||||||
|
const queryTemplate = `SELECT learning_type, course_thematic, organization_id, count(id) as count` +
|
||||||
|
` FROM courses` +
|
||||||
|
` WHERE 1=1`
|
||||||
|
|
||||||
|
query := queryTemplate
|
||||||
|
args := make([]any, 0, 3)
|
||||||
|
|
||||||
|
if params.LearningTypeID != "" {
|
||||||
|
query += ` AND learning_type = ?`
|
||||||
|
args = append(args, params.LearningTypeID)
|
||||||
|
}
|
||||||
|
if params.CourseThematicID != "" {
|
||||||
|
query += ` AND course_thematic = ?`
|
||||||
|
args = append(args, params.CourseThematicID)
|
||||||
|
}
|
||||||
|
if params.OrganizaitonID != "" {
|
||||||
|
query += ` AND organization_id = ?`
|
||||||
|
args = append(args, params.OrganizaitonID)
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ` GROUP BY learning_type, course_thematic, organization_id`
|
||||||
|
query += ` ORDER BY count(id) DESC`
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list courses.statistics",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
dbArgumentsAttr.StringSlice(argumentsAsStrings(args...)),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
} else {
|
||||||
|
span.SetStatus(codes.Ok, "finished")
|
||||||
|
}
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var stats []sqliteCourseStatistic
|
||||||
|
err = r.db.SelectContext(ctx, &stats, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.LearningTypeStatistics = xslices.Map(stats, asDomainFunc)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
id string,
|
||||||
|
) (course domain.Course, err error) {
|
||||||
|
const queryTemplate = `SELECT %s FROM courses WHERE id = ?`
|
||||||
|
|
||||||
|
query := fmt.Sprintf(queryTemplate, coursesFieldsStr)
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "get courses.courses",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var courseDB sqliteCourseDB
|
||||||
|
err = r.db.GetContext(ctx, &courseDB, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return course, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return courseDB.AsDomain(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) GetByExternalID(
|
||||||
|
ctx context.Context, id string,
|
||||||
|
) (course domain.Course, err error) {
|
||||||
|
return course, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) CreateBatch(ctx context.Context, params ...domain.CreateCourseParams) error {
|
||||||
|
tx, err := r.db.BeginTxx(ctx, &sql.TxOptions{Isolation: sql.LevelDefault})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("beginning tx: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
var errTx error
|
||||||
|
if err != nil {
|
||||||
|
errTx = tx.Rollback()
|
||||||
|
} else {
|
||||||
|
errTx = tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.Join(err, errTx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
const queryTempalate = `INSERT INTO courses` +
|
||||||
|
` (%s) VALUES (%s)`
|
||||||
|
|
||||||
|
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(coursesFields)), ",")
|
||||||
|
query := fmt.Sprintf(queryTempalate, coursesFieldsStr, placeholders)
|
||||||
|
|
||||||
|
stmt, err := tx.PrepareContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("preparing statement: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, param := range params {
|
||||||
|
_, err := stmt.ExecContext(ctx, createCourseParamsAsValues(param)...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing statement query: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) Create(ctx context.Context, params domain.CreateCourseParams) (domain.Course, error) {
|
||||||
|
err := r.CreateBatch(ctx, params)
|
||||||
|
return domain.Course{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) UpdateCourseDescription(ctx context.Context, id, description string) error {
|
||||||
|
return errors.New("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
return errors.New("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteCourseRepository) listCount(ctx context.Context, params domain.ListCoursesParams) (count int, err error) {
|
||||||
|
const queryTemplate = `SELECT COUNT(id) FROM courses WHERE 1=1`
|
||||||
|
|
||||||
|
query := queryTemplate
|
||||||
|
args := make([]any, 0, 6)
|
||||||
|
|
||||||
|
if params.LearningType != "" {
|
||||||
|
args = append(args, params.LearningType)
|
||||||
|
query += " AND learning_type = ?"
|
||||||
|
}
|
||||||
|
if params.CourseThematic != "" {
|
||||||
|
args = append(args, params.CourseThematic)
|
||||||
|
query += " AND course_thematic = ?"
|
||||||
|
}
|
||||||
|
if params.OrganizationID != "" {
|
||||||
|
args = append(args, params.OrganizationID)
|
||||||
|
query += " AND organization_id = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list_count courses.courses",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = r.db.GetContext(ctx, &count, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return count, fmt.Errorf("sending query: %w", err)
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rowsScanner interface {
|
||||||
|
sqlx.ColScanner
|
||||||
|
|
||||||
|
StructScan(dest any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanRows(ctx context.Context, db *sqlx.DB, f func(rowsScanner) error, query string, args ...any) error {
|
||||||
|
rows, err := db.QueryxContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("querying rows: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = errors.Join(err, rows.Close())
|
||||||
|
}()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
err = f(rows)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scanning row: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return fmt.Errorf("checking rows for errors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCourseParamsAsValues(params domain.CreateCourseParams) []any {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
return []any{
|
||||||
|
params.ID,
|
||||||
|
nullableValueAsString(params.ExternalID),
|
||||||
|
mapSourceTypeFromDomain(params.SourceType),
|
||||||
|
nullableValueAsString(params.SourceName),
|
||||||
|
params.CourseThematic,
|
||||||
|
params.LearningType,
|
||||||
|
params.OrganizationID,
|
||||||
|
params.OriginLink,
|
||||||
|
params.ImageLink,
|
||||||
|
params.Name,
|
||||||
|
params.Description,
|
||||||
|
params.FullPrice,
|
||||||
|
params.Discount,
|
||||||
|
params.Duration.Truncate(time.Second).Milliseconds() / 1000,
|
||||||
|
params.StartsAt,
|
||||||
|
now,
|
||||||
|
now,
|
||||||
|
sql.NullTime{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteCourseDB struct {
|
||||||
|
ID string `db:"id"`
|
||||||
|
ExternalID sql.NullString `db:"external_id"`
|
||||||
|
SourceType string `db:"source_type"`
|
||||||
|
SourceName sql.NullString `db:"source_name"`
|
||||||
|
ThematicID string `db:"course_thematic"`
|
||||||
|
LearningTypeID string `db:"learning_type"`
|
||||||
|
OrganizationID string `db:"organization_id"`
|
||||||
|
OriginLink string `db:"origin_link"`
|
||||||
|
ImageLink string `db:"image_link"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Description string `db:"description"`
|
||||||
|
FullPrice float64 `db:"full_price"`
|
||||||
|
Discount float64 `db:"discount"`
|
||||||
|
Duration int64 `db:"duration"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
StartsAt sql.NullTime `db:"starts_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
DeletedAt sql.NullTime `db:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteCourseStatistic struct {
|
||||||
|
LearningTypeID string `db:"learning_type"`
|
||||||
|
CourseThematicID string `db:"course_thematic"`
|
||||||
|
OrganizationID string `db:"organization_id"`
|
||||||
|
Count int `db:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sqliteCourseStatistic) AsDomain() domain.StatisticUnit {
|
||||||
|
return domain.StatisticUnit(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullStringAsDomain(s sql.NullString) nullable.Value[string] {
|
||||||
|
if s.Valid {
|
||||||
|
return nullable.NewValue(s.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullable.Value[string]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullTimeAsDomain(s sql.NullTime) nullable.Value[time.Time] {
|
||||||
|
if s.Valid {
|
||||||
|
return nullable.NewValue(s.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullable.Value[time.Time]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullableValueAsString(v nullable.Value[string]) sql.NullString {
|
||||||
|
return sql.NullString{
|
||||||
|
Valid: v.Valid(),
|
||||||
|
String: v.Value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullableValueAsTime(v nullable.Value[time.Time]) sql.NullTime {
|
||||||
|
return sql.NullTime{
|
||||||
|
Valid: v.Valid(),
|
||||||
|
Time: v.Value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c sqliteCourseDB) AsDomain() domain.Course {
|
||||||
|
return domain.Course{
|
||||||
|
ID: c.ID,
|
||||||
|
OrganizationID: c.OrganizationID,
|
||||||
|
OriginLink: c.OriginLink,
|
||||||
|
ImageLink: c.ImageLink,
|
||||||
|
Name: c.Name,
|
||||||
|
Description: c.Description,
|
||||||
|
FullPrice: c.FullPrice,
|
||||||
|
Discount: c.Discount,
|
||||||
|
ThematicID: c.ThematicID,
|
||||||
|
LearningTypeID: c.LearningTypeID,
|
||||||
|
Duration: time.Second * time.Duration(c.Duration),
|
||||||
|
StartsAt: c.StartsAt.Time,
|
||||||
|
CreatedAt: c.CreatedAt,
|
||||||
|
UpdatedAt: c.UpdatedAt,
|
||||||
|
ExternalID: nullStringAsDomain(c.ExternalID),
|
||||||
|
SourceType: mapSourceTypeToDomain(c.SourceType),
|
||||||
|
SourceName: nullStringAsDomain(c.SourceName),
|
||||||
|
DeletedAt: nullTimeAsDomain(c.DeletedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteCourseDB) FromDomain(d domain.Course) {
|
||||||
|
*c = sqliteCourseDB{
|
||||||
|
ID: d.ID,
|
||||||
|
OrganizationID: d.OrganizationID,
|
||||||
|
OriginLink: d.OriginLink,
|
||||||
|
ImageLink: d.ImageLink,
|
||||||
|
Name: d.Name,
|
||||||
|
Description: d.Description,
|
||||||
|
FullPrice: d.FullPrice,
|
||||||
|
Discount: d.Discount,
|
||||||
|
ThematicID: d.ThematicID,
|
||||||
|
LearningTypeID: d.LearningTypeID,
|
||||||
|
SourceType: mapSourceTypeFromDomain(d.SourceType),
|
||||||
|
Duration: d.Duration.Truncate(time.Second).Milliseconds() / 1000,
|
||||||
|
CreatedAt: d.CreatedAt,
|
||||||
|
UpdatedAt: d.UpdatedAt,
|
||||||
|
ExternalID: nullableValueAsString(d.ExternalID),
|
||||||
|
SourceName: nullableValueAsString(d.SourceName),
|
||||||
|
DeletedAt: nullableValueAsTime(d.DeletedAt),
|
||||||
|
StartsAt: sql.NullTime{
|
||||||
|
Time: d.StartsAt,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteCourseRepository) mergeAttributes(custom ...attribute.KeyValue) []attribute.KeyValue {
|
||||||
|
outbase := append(
|
||||||
|
getSqliteBaseAttributes(),
|
||||||
|
dbTableAttr.String("courses"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return append(outbase, custom...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func argumentsAsStrings(args ...any) []string {
|
||||||
|
out := make([]string, 0, len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
out = append(out, fmt.Sprintf("%v", arg))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
107
internal/kurious/adapters/sqlite_course_repository_test.go
Normal file
107
internal/kurious/adapters/sqlite_course_repository_test.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSqliteCourseRepository(t *testing.T) {
|
||||||
|
suite.Run(t, new(sqliteCourseRepositorySuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteCourseRepositorySuite struct {
|
||||||
|
sqliteBaseSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteCourseRepositorySuite) TestCreateCourse() {
|
||||||
|
expcourse := domain.Course{
|
||||||
|
ID: "test-id",
|
||||||
|
ExternalID: nullable.NewValue("ext-id"),
|
||||||
|
Name: "test-name",
|
||||||
|
SourceType: domain.SourceTypeParsed,
|
||||||
|
SourceName: nullable.NewValue("test-source"),
|
||||||
|
ThematicID: "test-thematic",
|
||||||
|
LearningTypeID: "test-learning",
|
||||||
|
OrganizationID: "test-org-id",
|
||||||
|
OriginLink: "test-link",
|
||||||
|
ImageLink: "test-image-link",
|
||||||
|
Description: "description",
|
||||||
|
FullPrice: 123,
|
||||||
|
Discount: 321,
|
||||||
|
Duration: time.Second * 360,
|
||||||
|
StartsAt: time.Date(2020, 10, 01, 11, 22, 33, 0, time.UTC),
|
||||||
|
Thematic: "",
|
||||||
|
LearningType: "",
|
||||||
|
CreatedAt: time.Time{},
|
||||||
|
UpdatedAt: time.Time{},
|
||||||
|
DeletedAt: nullable.Value[time.Time]{},
|
||||||
|
}
|
||||||
|
|
||||||
|
cr := s.connection.CourseRepository()
|
||||||
|
_, err := cr.Create(s.ctx, domain.CreateCourseParams{
|
||||||
|
ID: "test-id",
|
||||||
|
ExternalID: nullable.NewValue("ext-id"),
|
||||||
|
Name: "test-name",
|
||||||
|
SourceType: domain.SourceTypeParsed,
|
||||||
|
SourceName: nullable.NewValue("test-source"),
|
||||||
|
CourseThematic: "test-thematic",
|
||||||
|
LearningType: "test-learning",
|
||||||
|
OrganizationID: "test-org-id",
|
||||||
|
OriginLink: "test-link",
|
||||||
|
ImageLink: "test-image-link",
|
||||||
|
Description: "description",
|
||||||
|
FullPrice: 123,
|
||||||
|
Discount: 321,
|
||||||
|
Duration: time.Second * 360,
|
||||||
|
StartsAt: time.Date(2020, 10, 01, 11, 22, 33, 0, time.UTC),
|
||||||
|
})
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
gotCourse, err := cr.Get(s.ctx, expcourse.ID)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Require().NotEmpty(gotCourse.CreatedAt)
|
||||||
|
s.Require().NotEmpty(gotCourse.UpdatedAt)
|
||||||
|
s.Require().Empty(gotCourse.DeletedAt)
|
||||||
|
|
||||||
|
expcourse.CreatedAt = gotCourse.CreatedAt
|
||||||
|
expcourse.UpdatedAt = gotCourse.UpdatedAt
|
||||||
|
|
||||||
|
s.Require().Equal(expcourse, gotCourse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteCourseRepositorySuite) TestListLimitOffset() {
|
||||||
|
const coursecount = 9
|
||||||
|
const listparts = 3
|
||||||
|
basecourse := domain.CreateCourseParams{
|
||||||
|
SourceType: domain.SourceTypeManual,
|
||||||
|
}
|
||||||
|
|
||||||
|
cr := s.connection.CourseRepository()
|
||||||
|
for i := 0; i < coursecount; i++ {
|
||||||
|
basecourse.ID = strconv.Itoa(i)
|
||||||
|
_, err := cr.Create(s.ctx, basecourse)
|
||||||
|
s.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := domain.ListCoursesParams{
|
||||||
|
Limit: coursecount / listparts,
|
||||||
|
}
|
||||||
|
for i := 0; i < listparts; i++ {
|
||||||
|
result, err := cr.List(s.ctx, params)
|
||||||
|
s.NoError(err)
|
||||||
|
s.Len(result.Courses, listparts)
|
||||||
|
|
||||||
|
params.Offset += listparts
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := cr.List(s.ctx, params)
|
||||||
|
s.NoError(err)
|
||||||
|
s.Empty(result.Courses)
|
||||||
|
}
|
||||||
174
internal/kurious/adapters/sqlite_learning_category_repository.go
Normal file
174
internal/kurious/adapters/sqlite_learning_category_repository.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xslices"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
learningCategoryColumns = []string{
|
||||||
|
"id",
|
||||||
|
"logo",
|
||||||
|
"courses_count",
|
||||||
|
}
|
||||||
|
|
||||||
|
learningCategoryColumnsStr = joinColumns(learningCategoryColumns)
|
||||||
|
)
|
||||||
|
|
||||||
|
type learningCategoryDB struct {
|
||||||
|
ID string `db:"id"`
|
||||||
|
Logo sql.NullString `db:"logo"`
|
||||||
|
CoursesCount int `db:"courses_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l learningCategoryDB) AsDomain() domain.LearningCategory {
|
||||||
|
return domain.LearningCategory{
|
||||||
|
ID: l.ID,
|
||||||
|
Logo: nullStringAsDomain(l.Logo),
|
||||||
|
CoursesCount: l.CoursesCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConnection) LearningCategory() *sqliteLearingCategoryRepository {
|
||||||
|
return &sqliteLearingCategoryRepository{
|
||||||
|
db: c.db,
|
||||||
|
log: c.log.With("repository", "learning_categories"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteLearingCategoryRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteLearingCategoryRepository) Upsert(ctx context.Context, c domain.LearningCategory) (err error) {
|
||||||
|
const queryTemplate = "INSERT INTO learning_categories (%s)" +
|
||||||
|
" VALUES (%s)" +
|
||||||
|
" ON CONFLICT(id) DO UPDATE" +
|
||||||
|
" SET id = excluded.id" +
|
||||||
|
", logo = excluded.logo" +
|
||||||
|
", courses_count = excluded.courses_count"
|
||||||
|
|
||||||
|
query := fmt.Sprintf(
|
||||||
|
queryTemplate,
|
||||||
|
learningCategoryColumnsStr,
|
||||||
|
strings.TrimSuffix(strings.Repeat("?,", len(learningCategoryColumns)), ","),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "upsert courses.learning_categories",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("INSERT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = r.db.ExecContext(
|
||||||
|
ctx, query,
|
||||||
|
c.ID,
|
||||||
|
nullableValueAsString(c.Logo),
|
||||||
|
c.CoursesCount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteLearingCategoryRepository) List(ctx context.Context) (out []domain.LearningCategory, err error) {
|
||||||
|
const queryTemplate = "SELECT %s FROM learning_categories;"
|
||||||
|
|
||||||
|
query := fmt.Sprintf(queryTemplate, learningCategoryColumnsStr)
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list courses.learning_categories",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var categories []learningCategoryDB
|
||||||
|
err = r.db.SelectContext(ctx, &categories, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out = xslices.Map(categories, asDomainFunc)
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteLearingCategoryRepository) Get(ctx context.Context, id string) (category domain.LearningCategory, err error) {
|
||||||
|
const queryTemplate = "SELECT %s FROM learning_categories WHERE id = ?;"
|
||||||
|
|
||||||
|
query := fmt.Sprintf(queryTemplate, learningCategoryColumnsStr)
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "get courses.learning_categories",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var cdb learningCategoryDB
|
||||||
|
err = r.db.GetContext(ctx, &cdb, query, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return domain.LearningCategory{}, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
return domain.LearningCategory{}, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cdb.AsDomain(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteLearingCategoryRepository) mergeAttributes(custom ...attribute.KeyValue) []attribute.KeyValue {
|
||||||
|
outbase := append(
|
||||||
|
getSqliteBaseAttributes(),
|
||||||
|
dbTableAttr.String("learning_categories"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return append(outbase, custom...)
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSqliteLearningCategories(t *testing.T) {
|
||||||
|
suite.Run(t, new(sqliteLearningCategoriesRepositorySuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteLearningCategoriesRepositorySuite struct {
|
||||||
|
sqliteBaseSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteLearningCategoriesRepositorySuite) TestGet() {
|
||||||
|
var cdb learningCategoryDB
|
||||||
|
err := s.connection.db.GetContext(
|
||||||
|
s.ctx, &cdb,
|
||||||
|
"INSERT INTO learning_categories (id, logo, courses_count) VALUES (?,?,?) RETURNING *",
|
||||||
|
"test-id", "test-url-logo", 42,
|
||||||
|
)
|
||||||
|
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
expectedCategory := cdb.AsDomain()
|
||||||
|
|
||||||
|
lr := s.connection.LearningCategory()
|
||||||
|
got, err := lr.Get(s.ctx, expectedCategory.ID)
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(expectedCategory, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteLearningCategoriesRepositorySuite) TestList() {
|
||||||
|
stmt, err := s.connection.db.PrepareContext(s.ctx, "INSERT INTO learning_categories (id, logo, courses_count) VALUES (?,?,?)")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
for _, args := range [][]any{
|
||||||
|
{"test-id-1", "test-url-1", 1},
|
||||||
|
{"test-id-2", "test-url-2", 2},
|
||||||
|
} {
|
||||||
|
_, err = stmt.ExecContext(s.ctx, args...)
|
||||||
|
s.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotCategories, err := s.connection.LearningCategory().List(s.ctx)
|
||||||
|
s.NoError(err)
|
||||||
|
s.Len(gotCategories, 2)
|
||||||
|
|
||||||
|
expCategories := []domain.LearningCategory{
|
||||||
|
{
|
||||||
|
ID: "test-id-1",
|
||||||
|
Logo: nullable.NewValue("test-url-1"),
|
||||||
|
CoursesCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "test-id-2",
|
||||||
|
Logo: nullable.NewValue("test-url-2"),
|
||||||
|
CoursesCount: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotCategories[0].ID != "test-id-1" {
|
||||||
|
gotCategories[0], gotCategories[1] = gotCategories[1], gotCategories[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ElementsMatch(expCategories, gotCategories)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteLearningCategoriesRepositorySuite) TestUpsert() {
|
||||||
|
const categoryID = "test-id-1"
|
||||||
|
repo := s.connection.LearningCategory()
|
||||||
|
gotCategory, err := repo.Get(s.ctx, categoryID)
|
||||||
|
s.ErrorIs(err, domain.ErrNotFound)
|
||||||
|
s.Empty(gotCategory)
|
||||||
|
|
||||||
|
createdCategory := domain.LearningCategory{
|
||||||
|
ID: categoryID,
|
||||||
|
Logo: nullable.NewValue("test-url-1"),
|
||||||
|
CoursesCount: 1,
|
||||||
|
}
|
||||||
|
err = repo.Upsert(s.ctx, createdCategory)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
gotCategory, err = repo.Get(s.ctx, categoryID)
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(createdCategory, gotCategory)
|
||||||
|
|
||||||
|
createdCategory.Logo = nullable.NewValue("test-url-2")
|
||||||
|
|
||||||
|
err = repo.Upsert(s.ctx, createdCategory)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
gotCategory, err = repo.Get(s.ctx, categoryID)
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(createdCategory, gotCategory)
|
||||||
|
}
|
||||||
329
internal/kurious/adapters/sqlite_organization_repository.go
Normal file
329
internal/kurious/adapters/sqlite_organization_repository.go
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xslices"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
organizationColumns = []string{
|
||||||
|
"id",
|
||||||
|
"external_id",
|
||||||
|
"alias",
|
||||||
|
"name",
|
||||||
|
"site",
|
||||||
|
"logo",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"deleted_at",
|
||||||
|
}
|
||||||
|
|
||||||
|
organizationColumnsStr = joinColumns(organizationColumns)
|
||||||
|
organizationColumnsArgsStr = namedArgColumns(organizationColumns)
|
||||||
|
)
|
||||||
|
|
||||||
|
type organizationStatDB struct {
|
||||||
|
ID string `db:"id"`
|
||||||
|
ExternalID sql.NullString `db:"external_id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
CoursesCount uint64 `db:"courses_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s organizationStatDB) AsDomain() domain.OrganizationStat {
|
||||||
|
return domain.OrganizationStat{
|
||||||
|
ID: s.ID,
|
||||||
|
ExternalID: nullStringAsDomain(s.ExternalID),
|
||||||
|
Name: s.Name,
|
||||||
|
CoursesCount: s.CoursesCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type organizationDB struct {
|
||||||
|
ID string `db:"id"`
|
||||||
|
ExternalID sql.NullString `db:"external_id"`
|
||||||
|
Alias string `db:"alias"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Site string `db:"site"`
|
||||||
|
Logo string `db:"logo"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
DeletedAt sql.NullTime `db:"deleted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o organizationDB) AsDomain() domain.Organization {
|
||||||
|
return domain.Organization{
|
||||||
|
ID: o.ID,
|
||||||
|
ExternalID: nullStringAsDomain(o.ExternalID),
|
||||||
|
Alias: o.Alias,
|
||||||
|
Name: o.Name,
|
||||||
|
Site: o.Site,
|
||||||
|
LogoLink: o.Logo,
|
||||||
|
CreatedAt: o.CreatedAt,
|
||||||
|
UpdatedAt: o.UpdatedAt,
|
||||||
|
DeletedAt: nullTimeAsDomain(o.DeletedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *organizationDB) FromDomain(in domain.Organization) {
|
||||||
|
*o = organizationDB{
|
||||||
|
ID: in.ID,
|
||||||
|
ExternalID: nullableValueAsString(in.ExternalID),
|
||||||
|
Alias: in.Alias,
|
||||||
|
Name: in.Name,
|
||||||
|
Site: in.Site,
|
||||||
|
Logo: in.LogoLink,
|
||||||
|
CreatedAt: in.CreatedAt,
|
||||||
|
UpdatedAt: in.UpdatedAt,
|
||||||
|
DeletedAt: nullableValueAsTime(in.DeletedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConnection) Organization() domain.OrganizationRepository {
|
||||||
|
return &sqliteOrganizationRepository{
|
||||||
|
db: c.db,
|
||||||
|
log: c.log.With("repository", "organization"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteOrganizationRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteOrganizationRepository) ListStats(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListOrganizationsParams,
|
||||||
|
) (out []domain.OrganizationStat, err error) {
|
||||||
|
const queryTemplate = `WITH cte as (
|
||||||
|
SELECT learning_type, course_thematic, organization_id, count(id) as courses_count
|
||||||
|
FROM courses
|
||||||
|
WHERE 1=1 {whereSuffix}
|
||||||
|
GROUP BY learning_type, course_thematic, organization_id
|
||||||
|
) SELECT o.id as id, o.external_id as external_id, o.name as name, cte.courses_count as courses_count
|
||||||
|
FROM cte
|
||||||
|
INNER JOIN organizations o ON o.id = cte.organization_id`
|
||||||
|
|
||||||
|
whereSuffix := ""
|
||||||
|
|
||||||
|
args := make([]any, 0, 3)
|
||||||
|
if params.LearningTypeID != "" {
|
||||||
|
whereSuffix += " AND learning_type = ?"
|
||||||
|
args = append(args, params.LearningTypeID)
|
||||||
|
}
|
||||||
|
if params.CourseThematicID != "" {
|
||||||
|
whereSuffix += " AND course_thematic = ?"
|
||||||
|
args = append(args, params.CourseThematicID)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := strings.Replace(queryTemplate, "{whereSuffix}", whereSuffix, 1)
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list_stats courses",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var stats []organizationStatDB
|
||||||
|
err = r.db.SelectContext(ctx, &stats, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return xslices.Map(stats, asDomainFunc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteOrganizationRepository) List(ctx context.Context, params domain.ListOrganizationsParams) (out []domain.Organization, err error) {
|
||||||
|
const queryTemplate = `SELECT %s FROM organizations WHERE 1=1`
|
||||||
|
query := fmt.Sprintf(queryTemplate, organizationColumnsStr)
|
||||||
|
|
||||||
|
args := make([]any, 0, len(params.IDs))
|
||||||
|
if len(params.IDs) > 0 {
|
||||||
|
args = append(
|
||||||
|
args,
|
||||||
|
xslices.Map(params.IDs, func(t string) any { return t })...,
|
||||||
|
)
|
||||||
|
queryParam := strings.TrimSuffix(strings.Repeat("?,", len(args)), ",")
|
||||||
|
query += " AND id IN (" + queryParam + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "list courses.organizations",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
organizations := make([]organizationDB, 0, 1<<8)
|
||||||
|
err = r.db.SelectContext(ctx, &organizations, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return xslices.Map(organizations, asDomainFunc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteOrganizationRepository) Get(ctx context.Context, params domain.GetOrganizationParams) (out domain.Organization, err error) {
|
||||||
|
const queryTemplate = "SELECT %s FROM organizations WHERE 1=1"
|
||||||
|
query := fmt.Sprintf(queryTemplate, organizationColumnsStr)
|
||||||
|
args := make([]any, 0, 2)
|
||||||
|
if params.ID.Valid() {
|
||||||
|
args = append(args, params.ID.Value())
|
||||||
|
query += " AND id = ?"
|
||||||
|
}
|
||||||
|
if params.ExternalID.Valid() {
|
||||||
|
args = append(args, params.ExternalID.Value())
|
||||||
|
query += " AND external_id = ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "get courses.organizations",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("SELECT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var orgdb organizationDB
|
||||||
|
err = r.db.GetContext(ctx, &orgdb, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return out, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
return out, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgdb.AsDomain(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteOrganizationRepository) Create(ctx context.Context, params domain.CreateOrganizationParams) (out domain.Organization, err error) {
|
||||||
|
const queryTemplate = `INSERT INTO organizations (%[1]s) VALUES (%[2]s) RETURNING %[1]s`
|
||||||
|
query := fmt.Sprintf(queryTemplate, organizationColumnsStr, organizationColumnsArgsStr)
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "create courses.organizations",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("INSERT"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stmt, err := r.db.PrepareNamedContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return out, fmt.Errorf("preparing statement: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var orgdb organizationDB
|
||||||
|
|
||||||
|
now := time.Now().UTC().Truncate(time.Second)
|
||||||
|
err = stmt.GetContext(ctx, &orgdb, organizationDB{
|
||||||
|
ID: params.ID,
|
||||||
|
ExternalID: nullableValueAsString(params.ExternalID),
|
||||||
|
Alias: params.Alias,
|
||||||
|
Name: params.Name,
|
||||||
|
Site: params.Site,
|
||||||
|
Logo: params.LogoLink,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
DeletedAt: sql.NullTime{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return out, fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgdb.AsDomain(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteOrganizationRepository) Delete(ctx context.Context, id string) (err error) {
|
||||||
|
const query = `DELETE FROM organizations WHERE id = ?`
|
||||||
|
|
||||||
|
ctx, span := dbTracer.Start(
|
||||||
|
ctx, "delete courses.organizations",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(
|
||||||
|
r.mergeAttributes(
|
||||||
|
dbOperationAttr.String("DELETE"),
|
||||||
|
dbStatementAttr.String(query),
|
||||||
|
)...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
result, err := r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return domain.ErrNotFound
|
||||||
|
}
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, _ := result.RowsAffected()
|
||||||
|
if affected == 0 {
|
||||||
|
return domain.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteOrganizationRepository) mergeAttributes(custom ...attribute.KeyValue) []attribute.KeyValue {
|
||||||
|
outbase := append(
|
||||||
|
getSqliteBaseAttributes(),
|
||||||
|
dbTableAttr.String("organizaitons"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return append(outbase, custom...)
|
||||||
|
}
|
||||||
128
internal/kurious/adapters/sqlite_organization_repository_test.go
Normal file
128
internal/kurious/adapters/sqlite_organization_repository_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSqilteOrganizations(t *testing.T) {
|
||||||
|
suite.Run(t, new(sqliteOrganzationRepositorySuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteOrganzationRepositorySuite struct {
|
||||||
|
sqliteBaseSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteOrganzationRepositorySuite) TearDownTest() {
|
||||||
|
_ = s.connection.db.MustExecContext(s.ctx, "DELETE FROM organizations")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteOrganzationRepositorySuite) TestList() {
|
||||||
|
const itemscount = 3
|
||||||
|
orgsdb := make([]domain.Organization, 0, itemscount)
|
||||||
|
|
||||||
|
baseOrg := domain.Organization{
|
||||||
|
Alias: "test-alias",
|
||||||
|
Name: "test-name",
|
||||||
|
Site: "test-site",
|
||||||
|
LogoLink: "test-logo",
|
||||||
|
}
|
||||||
|
for i := 0; i < itemscount; i++ {
|
||||||
|
nextitem := baseOrg
|
||||||
|
iStr := strconv.Itoa(i)
|
||||||
|
nextitem.ID = "test-id-" + iStr
|
||||||
|
nextitem.ExternalID.Set("test-ext-id-" + iStr)
|
||||||
|
|
||||||
|
gotOrg, err := s.connection.Organization().Create(s.ctx, domain.CreateOrganizationParams{
|
||||||
|
ID: nextitem.ID,
|
||||||
|
ExternalID: nextitem.ExternalID,
|
||||||
|
Alias: nextitem.Alias,
|
||||||
|
Name: nextitem.Name,
|
||||||
|
Site: nextitem.Site,
|
||||||
|
LogoLink: nextitem.LogoLink,
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
orgsdb = append(orgsdb, gotOrg)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotOrgs, err := s.connection.Organization().List(s.ctx, domain.ListOrganizationsParams{})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
compareF := func(lhs, rhs domain.Organization) int {
|
||||||
|
if lhs.ID < rhs.ID {
|
||||||
|
return -1
|
||||||
|
} else if lhs.ID > rhs.ID {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.SortFunc(gotOrgs, compareF)
|
||||||
|
|
||||||
|
for i := range gotOrgs {
|
||||||
|
s.NotEmpty(gotOrgs[i].CreatedAt)
|
||||||
|
s.NotEmpty(gotOrgs[i].UpdatedAt)
|
||||||
|
s.Empty(gotOrgs[i].DeletedAt)
|
||||||
|
|
||||||
|
orgsdb[i].CreatedAt = gotOrgs[i].CreatedAt
|
||||||
|
orgsdb[i].UpdatedAt = gotOrgs[i].UpdatedAt
|
||||||
|
orgsdb[i].DeletedAt = gotOrgs[i].DeletedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ElementsMatch(orgsdb, gotOrgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteOrganzationRepositorySuite) TestGet() {
|
||||||
|
var orgdb organizationDB
|
||||||
|
err := s.connection.db.GetContext(
|
||||||
|
s.ctx, &orgdb,
|
||||||
|
`INSERT INTO organizations (`+organizationColumnsStr+`)`+
|
||||||
|
` VALUES ("test-id", "test-ext-id", "test-alias", "test-name", "test-site", "test-logo", CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`+
|
||||||
|
` RETURNING `+organizationColumnsStr,
|
||||||
|
)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
expectedOrganization := orgdb.AsDomain()
|
||||||
|
|
||||||
|
getParams := domain.GetOrganizationParams{
|
||||||
|
ID: nullable.NewValue(orgdb.ID),
|
||||||
|
}
|
||||||
|
gotOrganization, err := s.connection.Organization().Get(s.ctx, getParams)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
s.Equal(expectedOrganization, gotOrganization)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sqliteOrganzationRepositorySuite) TestCreate() {
|
||||||
|
expectedOrganization := domain.Organization{
|
||||||
|
ID: "test-id",
|
||||||
|
ExternalID: nullable.NewValue("test-ext-id"),
|
||||||
|
Alias: "test-alias",
|
||||||
|
Name: "test-name",
|
||||||
|
Site: "test-site",
|
||||||
|
LogoLink: "test-logo",
|
||||||
|
}
|
||||||
|
|
||||||
|
gotOrganization, err := s.connection.Organization().Create(s.ctx, domain.CreateOrganizationParams{
|
||||||
|
ID: expectedOrganization.ID,
|
||||||
|
ExternalID: expectedOrganization.ExternalID,
|
||||||
|
Alias: expectedOrganization.Alias,
|
||||||
|
Name: expectedOrganization.Name,
|
||||||
|
Site: expectedOrganization.Site,
|
||||||
|
LogoLink: expectedOrganization.LogoLink,
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
s.NotEmpty(gotOrganization.CreatedAt)
|
||||||
|
s.NotEmpty(gotOrganization.UpdatedAt)
|
||||||
|
s.Empty(gotOrganization.DeletedAt)
|
||||||
|
|
||||||
|
expectedOrganization.CreatedAt = gotOrganization.CreatedAt
|
||||||
|
expectedOrganization.UpdatedAt = gotOrganization.UpdatedAt
|
||||||
|
s.Equal(expectedOrganization, gotOrganization)
|
||||||
|
}
|
||||||
@ -109,6 +109,14 @@ func (conn *YDBConnection) Close() error {
|
|||||||
return conn.Driver.Close(ctx)
|
return conn.Driver.Close(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conn *YDBConnection) Organization() domain.OrganizationRepository {
|
||||||
|
return domain.NotImplementedOrganizationRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *YDBConnection) LearningCategory() domain.LearningCategoryRepository {
|
||||||
|
return domain.NotImplementedLearningCategory{}
|
||||||
|
}
|
||||||
|
|
||||||
func (conn *YDBConnection) CourseRepository() *ydbCourseRepository {
|
func (conn *YDBConnection) CourseRepository() *ydbCourseRepository {
|
||||||
return &ydbCourseRepository{
|
return &ydbCourseRepository{
|
||||||
db: conn.Driver,
|
db: conn.Driver,
|
||||||
@ -170,7 +178,6 @@ func (r *ydbCourseRepository) List(
|
|||||||
|
|
||||||
opts = append(
|
opts = append(
|
||||||
opts,
|
opts,
|
||||||
table.ValueParam("$id", types.TextValue(params.NextPageToken)),
|
|
||||||
table.ValueParam("$limit", types.Int32Value(int32(params.Limit))),
|
table.ValueParam("$limit", types.Int32Value(int32(params.Limit))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ type Commands struct {
|
|||||||
InsertCourse command.CreateCourseHandler
|
InsertCourse command.CreateCourseHandler
|
||||||
DeleteCourse command.DeleteCourseHandler
|
DeleteCourse command.DeleteCourseHandler
|
||||||
UpdateCourseDescription command.UpdateCourseDescriptionHandler
|
UpdateCourseDescription command.UpdateCourseDescriptionHandler
|
||||||
|
|
||||||
|
InsertOrganization command.CreateOrganizationHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
type Queries struct {
|
type Queries struct {
|
||||||
@ -17,6 +19,11 @@ type Queries struct {
|
|||||||
ListCourses query.ListCourseHandler
|
ListCourses query.ListCourseHandler
|
||||||
ListLearningTypes query.ListLearningTypesHandler
|
ListLearningTypes query.ListLearningTypesHandler
|
||||||
ListCourseThematics query.ListCourseThematicsHandler
|
ListCourseThematics query.ListCourseThematicsHandler
|
||||||
|
ListCourseStatistics query.ListCoursesStatsHandler
|
||||||
|
|
||||||
|
ListOrganzations query.ListOrganizationsHandler
|
||||||
|
ListOrganizationsStats query.ListOrganizationsStatsHandler
|
||||||
|
GetOrganization query.GetOrganizationHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
|
|||||||
53
internal/kurious/app/command/createorganization.go
Normal file
53
internal/kurious/app/command/createorganization.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/decorator"
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateOrganization struct {
|
||||||
|
ID string
|
||||||
|
ExternalID nullable.Value[string]
|
||||||
|
Alias string
|
||||||
|
Name string
|
||||||
|
Site string
|
||||||
|
Logo string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateOrganizationHandler decorator.CommandHandler[CreateOrganization]
|
||||||
|
|
||||||
|
type createOrganizationHandler struct {
|
||||||
|
repo domain.OrganizationRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateOrganizationHandler(
|
||||||
|
repo domain.OrganizationRepository,
|
||||||
|
log *slog.Logger,
|
||||||
|
) CreateOrganizationHandler {
|
||||||
|
h := createOrganizationHandler{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorator.ApplyCommandDecorators(h, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h createOrganizationHandler) Handle(ctx context.Context, cmd CreateOrganization) error {
|
||||||
|
_, err := h.repo.Create(ctx, domain.CreateOrganizationParams{
|
||||||
|
ID: cmd.ID,
|
||||||
|
ExternalID: cmd.ExternalID,
|
||||||
|
Alias: cmd.Alias,
|
||||||
|
Name: cmd.Name,
|
||||||
|
Site: cmd.Site,
|
||||||
|
LogoLink: cmd.Logo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
45
internal/kurious/app/query/getorganization.go
Normal file
45
internal/kurious/app/query/getorganization.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/decorator"
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetOrganization struct {
|
||||||
|
ID nullable.Value[string]
|
||||||
|
ExternalID nullable.Value[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetOrganizationHandler decorator.QueryHandler[GetOrganization, domain.Organization]
|
||||||
|
|
||||||
|
type getOrganizationHandler struct {
|
||||||
|
repo domain.OrganizationRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetOrganizationHandler(
|
||||||
|
repo domain.OrganizationRepository,
|
||||||
|
log *slog.Logger,
|
||||||
|
) GetOrganizationHandler {
|
||||||
|
h := getOrganizationHandler{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorator.AddQueryDecorators(h, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h getOrganizationHandler) Handle(ctx context.Context, query GetOrganization) (domain.Organization, error) {
|
||||||
|
organization, err := h.repo.Get(ctx, domain.GetOrganizationParams{
|
||||||
|
ID: query.ID,
|
||||||
|
ExternalID: query.ExternalID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.Organization{}, fmt.Errorf("getting organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return organization, nil
|
||||||
|
}
|
||||||
@ -17,7 +17,11 @@ type ListCourse struct {
|
|||||||
OrganizationID string
|
OrganizationID string
|
||||||
Keyword string
|
Keyword string
|
||||||
|
|
||||||
|
OrderBy string
|
||||||
|
Ascending bool
|
||||||
|
|
||||||
Limit int
|
Limit int
|
||||||
|
Offset int
|
||||||
NextPageToken string
|
NextPageToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +45,13 @@ func NewListCourseHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h listCourseHandler) Handle(ctx context.Context, query ListCourse) (out domain.ListCoursesResult, err error) {
|
func (h listCourseHandler) Handle(ctx context.Context, query ListCourse) (out domain.ListCoursesResult, err error) {
|
||||||
out.NextPageToken = query.NextPageToken
|
const defaultBatchSize = 1_000
|
||||||
|
|
||||||
drainFull := query.Limit == 0
|
drainFull := query.Limit == 0
|
||||||
if !drainFull {
|
if !drainFull {
|
||||||
out.Courses = make([]domain.Course, 0, query.Limit)
|
out.Courses = make([]domain.Course, 0, query.Limit)
|
||||||
|
} else {
|
||||||
|
query.Limit = defaultBatchSize
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
result, err := h.repo.List(ctx, domain.ListCoursesParams{
|
result, err := h.repo.List(ctx, domain.ListCoursesParams{
|
||||||
@ -52,8 +59,9 @@ func (h listCourseHandler) Handle(ctx context.Context, query ListCourse) (out do
|
|||||||
LearningType: query.LearningType,
|
LearningType: query.LearningType,
|
||||||
OrganizationID: query.OrganizationID,
|
OrganizationID: query.OrganizationID,
|
||||||
Limit: query.Limit,
|
Limit: query.Limit,
|
||||||
|
Offset: query.Offset,
|
||||||
NextPageToken: out.NextPageToken,
|
OrderBy: query.OrderBy,
|
||||||
|
Ascending: query.Ascending,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, fmt.Errorf("listing courses: %w", err)
|
return out, fmt.Errorf("listing courses: %w", err)
|
||||||
@ -67,9 +75,10 @@ func (h listCourseHandler) Handle(ctx context.Context, query ListCourse) (out do
|
|||||||
})
|
})
|
||||||
|
|
||||||
out.Courses = append(out.Courses, result.Courses...)
|
out.Courses = append(out.Courses, result.Courses...)
|
||||||
out.NextPageToken = result.NextPageToken
|
out.Count = result.Count
|
||||||
|
|
||||||
if drainFull && len(result.Courses) > 0 && result.NextPageToken != "" {
|
if drainFull && len(result.Courses) == query.Limit {
|
||||||
|
query.Offset += query.Limit
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
69
internal/kurious/app/query/listcoursesstats.go
Normal file
69
internal/kurious/app/query/listcoursesstats.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/decorator"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListCoursesStats struct {
|
||||||
|
LearningTypeID string
|
||||||
|
CourseThematicsID string
|
||||||
|
OrganizationID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListCoursesStatsResult struct{}
|
||||||
|
|
||||||
|
type ListCoursesStatsHandler decorator.QueryHandler[ListCoursesStats, domain.ListCoursesStatsResult]
|
||||||
|
|
||||||
|
type listCoursesStatsHandler struct {
|
||||||
|
repo domain.CourseRepository
|
||||||
|
mapper domain.CourseMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListCoursesStatsHandler(
|
||||||
|
mapper domain.CourseMapper,
|
||||||
|
repo domain.CourseRepository,
|
||||||
|
log *slog.Logger,
|
||||||
|
) ListCoursesStatsHandler {
|
||||||
|
h := listCoursesStatsHandler{
|
||||||
|
repo: repo,
|
||||||
|
mapper: mapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorator.AddQueryDecorators(h, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h listCoursesStatsHandler) Handle(
|
||||||
|
ctx context.Context,
|
||||||
|
query ListCoursesStats,
|
||||||
|
) (out domain.ListCoursesStatsResult, err error) {
|
||||||
|
if query.OrganizationID != "" {
|
||||||
|
statistics, err := h.repo.ListStatistics(ctx, domain.ListStatisticsParams{
|
||||||
|
LearningTypeID: query.LearningTypeID,
|
||||||
|
CourseThematicID: query.CourseThematicsID,
|
||||||
|
OrganizaitonID: query.OrganizationID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return out, fmt.Errorf("listing statistics: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.StatsByLearningType = make(map[string]domain.LearningTypeStat, len(statistics.LearningTypeStatistics))
|
||||||
|
for _, unit := range statistics.LearningTypeStatistics {
|
||||||
|
stats, ok := out.StatsByLearningType[unit.LearningTypeID]
|
||||||
|
stats.Count++
|
||||||
|
if !ok {
|
||||||
|
stats.CourseThematic = make(map[string]int)
|
||||||
|
}
|
||||||
|
stats.CourseThematic[unit.CourseThematicID]++
|
||||||
|
out.StatsByLearningType[unit.LearningTypeID] = stats
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stats := h.mapper.GetStats(false)
|
||||||
|
out.StatsByLearningType = stats
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
42
internal/kurious/app/query/listorganizations.go
Normal file
42
internal/kurious/app/query/listorganizations.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/decorator"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListOrganizations struct {
|
||||||
|
IDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListOrganizationsHandler decorator.QueryHandler[ListOrganizations, []domain.Organization]
|
||||||
|
|
||||||
|
type listOrganizationsHandler struct {
|
||||||
|
repo domain.OrganizationRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListOrganizationsHandler(
|
||||||
|
repo domain.OrganizationRepository,
|
||||||
|
log *slog.Logger,
|
||||||
|
) ListOrganizationsHandler {
|
||||||
|
h := listOrganizationsHandler{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorator.AddQueryDecorators(h, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h listOrganizationsHandler) Handle(ctx context.Context, query ListOrganizations) ([]domain.Organization, error) {
|
||||||
|
organizations, err := h.repo.List(ctx, domain.ListOrganizationsParams{
|
||||||
|
IDs: query.IDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing organizations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return organizations, nil
|
||||||
|
}
|
||||||
52
internal/kurious/app/query/listorganizationstats.go
Normal file
52
internal/kurious/app/query/listorganizationstats.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/decorator"
|
||||||
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListOrganizationsStats struct {
|
||||||
|
LearningTypeID string
|
||||||
|
CourseThematicID string
|
||||||
|
IDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListOrganizationsStatsHandler decorator.QueryHandler[
|
||||||
|
ListOrganizationsStats,
|
||||||
|
[]domain.OrganizationStat,
|
||||||
|
]
|
||||||
|
|
||||||
|
type listOrganizationsStatsHandler struct {
|
||||||
|
repo domain.OrganizationRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListOrganizationsStatsHandler(
|
||||||
|
repo domain.OrganizationRepository,
|
||||||
|
log *slog.Logger,
|
||||||
|
) ListOrganizationsStatsHandler {
|
||||||
|
h := listOrganizationsStatsHandler{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorator.AddQueryDecorators(h, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h listOrganizationsStatsHandler) Handle(
|
||||||
|
ctx context.Context,
|
||||||
|
query ListOrganizationsStats,
|
||||||
|
) ([]domain.OrganizationStat, error) {
|
||||||
|
stats, err := h.repo.ListStats(ctx, domain.ListOrganizationsParams{
|
||||||
|
LearningTypeID: query.LearningTypeID,
|
||||||
|
CourseThematicID: query.CourseThematicID,
|
||||||
|
IDs: query.IDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing stats: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
9
internal/kurious/domain/category.go
Normal file
9
internal/kurious/domain/category.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "git.loyso.art/frx/kurious/internal/common/nullable"
|
||||||
|
|
||||||
|
type LearningCategory struct {
|
||||||
|
ID string
|
||||||
|
Logo nullable.Value[string]
|
||||||
|
CoursesCount int
|
||||||
|
}
|
||||||
12
internal/kurious/domain/error.go
Normal file
12
internal/kurious/domain/error.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrNotFound PlainError = "not found"
|
||||||
|
ErrNotImplemented PlainError = "not implemented"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlainError string
|
||||||
|
|
||||||
|
func (err PlainError) Error() string {
|
||||||
|
return string(err)
|
||||||
|
}
|
||||||
@ -1,6 +1,12 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
type CourseMapper interface {
|
type CourseMapper interface {
|
||||||
CourseThematicNameByID(string) string
|
CourseThematicNameByID(string) string
|
||||||
LearningTypeNameByID(string) string
|
LearningTypeNameByID(string) string
|
||||||
|
|
||||||
|
CollectCounts(context.Context, CourseRepository) error
|
||||||
|
GetCounts(byCourseThematic, byLearningType string) int
|
||||||
|
GetStats(copyMap bool) map[string]LearningTypeStat
|
||||||
}
|
}
|
||||||
|
|||||||
534
internal/kurious/domain/mocks/CourseRepository.go
Normal file
534
internal/kurious/domain/mocks/CourseRepository.go
Normal file
@ -0,0 +1,534 @@
|
|||||||
|
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
domain "git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CourseRepository is an autogenerated mock type for the CourseRepository type
|
||||||
|
type CourseRepository struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type CourseRepository_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *CourseRepository) EXPECT() *CourseRepository_Expecter {
|
||||||
|
return &CourseRepository_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *CourseRepository) Create(_a0 context.Context, _a1 domain.CreateCourseParams) (domain.Course, error) {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Create")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.Course
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.CreateCourseParams) (domain.Course, error)); ok {
|
||||||
|
return rf(_a0, _a1)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.CreateCourseParams) domain.Course); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.Course)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, domain.CreateCourseParams) error); ok {
|
||||||
|
r1 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||||
|
type CourseRepository_Create_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 domain.CreateCourseParams
|
||||||
|
func (_e *CourseRepository_Expecter) Create(_a0 interface{}, _a1 interface{}) *CourseRepository_Create_Call {
|
||||||
|
return &CourseRepository_Create_Call{Call: _e.mock.On("Create", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Create_Call) Run(run func(_a0 context.Context, _a1 domain.CreateCourseParams)) *CourseRepository_Create_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(domain.CreateCourseParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Create_Call) Return(_a0 domain.Course, _a1 error) *CourseRepository_Create_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Create_Call) RunAndReturn(run func(context.Context, domain.CreateCourseParams) (domain.Course, error)) *CourseRepository_Create_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBatch provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *CourseRepository) CreateBatch(_a0 context.Context, _a1 ...domain.CreateCourseParams) error {
|
||||||
|
_va := make([]interface{}, len(_a1))
|
||||||
|
for _i := range _a1 {
|
||||||
|
_va[_i] = _a1[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, _a0)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for CreateBatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, ...domain.CreateCourseParams) error); ok {
|
||||||
|
r0 = rf(_a0, _a1...)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_CreateBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateBatch'
|
||||||
|
type CourseRepository_CreateBatch_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBatch is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 ...domain.CreateCourseParams
|
||||||
|
func (_e *CourseRepository_Expecter) CreateBatch(_a0 interface{}, _a1 ...interface{}) *CourseRepository_CreateBatch_Call {
|
||||||
|
return &CourseRepository_CreateBatch_Call{Call: _e.mock.On("CreateBatch",
|
||||||
|
append([]interface{}{_a0}, _a1...)...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_CreateBatch_Call) Run(run func(_a0 context.Context, _a1 ...domain.CreateCourseParams)) *CourseRepository_CreateBatch_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
variadicArgs := make([]domain.CreateCourseParams, len(args)-1)
|
||||||
|
for i, a := range args[1:] {
|
||||||
|
if a != nil {
|
||||||
|
variadicArgs[i] = a.(domain.CreateCourseParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run(args[0].(context.Context), variadicArgs...)
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_CreateBatch_Call) Return(_a0 error) *CourseRepository_CreateBatch_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_CreateBatch_Call) RunAndReturn(run func(context.Context, ...domain.CreateCourseParams) error) *CourseRepository_CreateBatch_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete provides a mock function with given fields: ctx, id
|
||||||
|
func (_m *CourseRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
ret := _m.Called(ctx, id)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||||
|
type CourseRepository_Delete_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
func (_e *CourseRepository_Expecter) Delete(ctx interface{}, id interface{}) *CourseRepository_Delete_Call {
|
||||||
|
return &CourseRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Delete_Call) Run(run func(ctx context.Context, id string)) *CourseRepository_Delete_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Delete_Call) Return(_a0 error) *CourseRepository_Delete_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Delete_Call) RunAndReturn(run func(context.Context, string) error) *CourseRepository_Delete_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provides a mock function with given fields: ctx, id
|
||||||
|
func (_m *CourseRepository) Get(ctx context.Context, id string) (domain.Course, error) {
|
||||||
|
ret := _m.Called(ctx, id)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Get")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.Course
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) (domain.Course, error)); ok {
|
||||||
|
return rf(ctx, id)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) domain.Course); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.Course)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||||
|
r1 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||||
|
type CourseRepository_Get_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
func (_e *CourseRepository_Expecter) Get(ctx interface{}, id interface{}) *CourseRepository_Get_Call {
|
||||||
|
return &CourseRepository_Get_Call{Call: _e.mock.On("Get", ctx, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Get_Call) Run(run func(ctx context.Context, id string)) *CourseRepository_Get_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Get_Call) Return(_a0 domain.Course, _a1 error) *CourseRepository_Get_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_Get_Call) RunAndReturn(run func(context.Context, string) (domain.Course, error)) *CourseRepository_Get_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByExternalID provides a mock function with given fields: ctx, id
|
||||||
|
func (_m *CourseRepository) GetByExternalID(ctx context.Context, id string) (domain.Course, error) {
|
||||||
|
ret := _m.Called(ctx, id)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for GetByExternalID")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.Course
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) (domain.Course, error)); ok {
|
||||||
|
return rf(ctx, id)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) domain.Course); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.Course)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||||
|
r1 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_GetByExternalID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByExternalID'
|
||||||
|
type CourseRepository_GetByExternalID_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByExternalID is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
func (_e *CourseRepository_Expecter) GetByExternalID(ctx interface{}, id interface{}) *CourseRepository_GetByExternalID_Call {
|
||||||
|
return &CourseRepository_GetByExternalID_Call{Call: _e.mock.On("GetByExternalID", ctx, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_GetByExternalID_Call) Run(run func(ctx context.Context, id string)) *CourseRepository_GetByExternalID_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_GetByExternalID_Call) Return(_a0 domain.Course, _a1 error) *CourseRepository_GetByExternalID_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_GetByExternalID_Call) RunAndReturn(run func(context.Context, string) (domain.Course, error)) *CourseRepository_GetByExternalID_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// List provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *CourseRepository) List(_a0 context.Context, _a1 domain.ListCoursesParams) (domain.ListCoursesResult, error) {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for List")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.ListCoursesResult
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.ListCoursesParams) (domain.ListCoursesResult, error)); ok {
|
||||||
|
return rf(_a0, _a1)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.ListCoursesParams) domain.ListCoursesResult); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.ListCoursesResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, domain.ListCoursesParams) error); ok {
|
||||||
|
r1 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
|
||||||
|
type CourseRepository_List_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 domain.ListCoursesParams
|
||||||
|
func (_e *CourseRepository_Expecter) List(_a0 interface{}, _a1 interface{}) *CourseRepository_List_Call {
|
||||||
|
return &CourseRepository_List_Call{Call: _e.mock.On("List", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_List_Call) Run(run func(_a0 context.Context, _a1 domain.ListCoursesParams)) *CourseRepository_List_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(domain.ListCoursesParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_List_Call) Return(_a0 domain.ListCoursesResult, _a1 error) *CourseRepository_List_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_List_Call) RunAndReturn(run func(context.Context, domain.ListCoursesParams) (domain.ListCoursesResult, error)) *CourseRepository_List_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCourseThematics provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *CourseRepository) ListCourseThematics(_a0 context.Context, _a1 domain.ListCourseThematicsParams) (domain.ListCourseThematicsResult, error) {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ListCourseThematics")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.ListCourseThematicsResult
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.ListCourseThematicsParams) (domain.ListCourseThematicsResult, error)); ok {
|
||||||
|
return rf(_a0, _a1)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.ListCourseThematicsParams) domain.ListCourseThematicsResult); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.ListCourseThematicsResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, domain.ListCourseThematicsParams) error); ok {
|
||||||
|
r1 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_ListCourseThematics_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCourseThematics'
|
||||||
|
type CourseRepository_ListCourseThematics_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCourseThematics is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 domain.ListCourseThematicsParams
|
||||||
|
func (_e *CourseRepository_Expecter) ListCourseThematics(_a0 interface{}, _a1 interface{}) *CourseRepository_ListCourseThematics_Call {
|
||||||
|
return &CourseRepository_ListCourseThematics_Call{Call: _e.mock.On("ListCourseThematics", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_ListCourseThematics_Call) Run(run func(_a0 context.Context, _a1 domain.ListCourseThematicsParams)) *CourseRepository_ListCourseThematics_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(domain.ListCourseThematicsParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_ListCourseThematics_Call) Return(_a0 domain.ListCourseThematicsResult, _a1 error) *CourseRepository_ListCourseThematics_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_ListCourseThematics_Call) RunAndReturn(run func(context.Context, domain.ListCourseThematicsParams) (domain.ListCourseThematicsResult, error)) *CourseRepository_ListCourseThematics_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLearningTypes provides a mock function with given fields: _a0
|
||||||
|
func (_m *CourseRepository) ListLearningTypes(_a0 context.Context) (domain.ListLearningTypeResult, error) {
|
||||||
|
ret := _m.Called(_a0)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ListLearningTypes")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.ListLearningTypeResult
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) (domain.ListLearningTypeResult, error)); ok {
|
||||||
|
return rf(_a0)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) domain.ListLearningTypeResult); ok {
|
||||||
|
r0 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.ListLearningTypeResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||||
|
r1 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_ListLearningTypes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListLearningTypes'
|
||||||
|
type CourseRepository_ListLearningTypes_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLearningTypes is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
func (_e *CourseRepository_Expecter) ListLearningTypes(_a0 interface{}) *CourseRepository_ListLearningTypes_Call {
|
||||||
|
return &CourseRepository_ListLearningTypes_Call{Call: _e.mock.On("ListLearningTypes", _a0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_ListLearningTypes_Call) Run(run func(_a0 context.Context)) *CourseRepository_ListLearningTypes_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_ListLearningTypes_Call) Return(_a0 domain.ListLearningTypeResult, _a1 error) *CourseRepository_ListLearningTypes_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_ListLearningTypes_Call) RunAndReturn(run func(context.Context) (domain.ListLearningTypeResult, error)) *CourseRepository_ListLearningTypes_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCourseDescription provides a mock function with given fields: ctx, id, description
|
||||||
|
func (_m *CourseRepository) UpdateCourseDescription(ctx context.Context, id string, description string) error {
|
||||||
|
ret := _m.Called(ctx, id, description)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for UpdateCourseDescription")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||||
|
r0 = rf(ctx, id, description)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseRepository_UpdateCourseDescription_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCourseDescription'
|
||||||
|
type CourseRepository_UpdateCourseDescription_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCourseDescription is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
// - description string
|
||||||
|
func (_e *CourseRepository_Expecter) UpdateCourseDescription(ctx interface{}, id interface{}, description interface{}) *CourseRepository_UpdateCourseDescription_Call {
|
||||||
|
return &CourseRepository_UpdateCourseDescription_Call{Call: _e.mock.On("UpdateCourseDescription", ctx, id, description)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_UpdateCourseDescription_Call) Run(run func(ctx context.Context, id string, description string)) *CourseRepository_UpdateCourseDescription_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_UpdateCourseDescription_Call) Return(_a0 error) *CourseRepository_UpdateCourseDescription_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *CourseRepository_UpdateCourseDescription_Call) RunAndReturn(run func(context.Context, string, string) error) *CourseRepository_UpdateCourseDescription_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCourseRepository creates a new instance of CourseRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewCourseRepository(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *CourseRepository {
|
||||||
|
mock := &CourseRepository{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
199
internal/kurious/domain/mocks/LearningCategoryRepository.go
Normal file
199
internal/kurious/domain/mocks/LearningCategoryRepository.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
domain "git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LearningCategoryRepository is an autogenerated mock type for the LearningCategoryRepository type
|
||||||
|
type LearningCategoryRepository struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type LearningCategoryRepository_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *LearningCategoryRepository) EXPECT() *LearningCategoryRepository_Expecter {
|
||||||
|
return &LearningCategoryRepository_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *LearningCategoryRepository) Get(_a0 context.Context, _a1 string) (domain.LearningCategory, error) {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Get")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.LearningCategory
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) (domain.LearningCategory, error)); ok {
|
||||||
|
return rf(_a0, _a1)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) domain.LearningCategory); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.LearningCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||||
|
r1 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// LearningCategoryRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||||
|
type LearningCategoryRepository_Get_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 string
|
||||||
|
func (_e *LearningCategoryRepository_Expecter) Get(_a0 interface{}, _a1 interface{}) *LearningCategoryRepository_Get_Call {
|
||||||
|
return &LearningCategoryRepository_Get_Call{Call: _e.mock.On("Get", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_Get_Call) Run(run func(_a0 context.Context, _a1 string)) *LearningCategoryRepository_Get_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_Get_Call) Return(_a0 domain.LearningCategory, _a1 error) *LearningCategoryRepository_Get_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_Get_Call) RunAndReturn(run func(context.Context, string) (domain.LearningCategory, error)) *LearningCategoryRepository_Get_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// List provides a mock function with given fields: _a0
|
||||||
|
func (_m *LearningCategoryRepository) List(_a0 context.Context) ([]domain.LearningCategory, error) {
|
||||||
|
ret := _m.Called(_a0)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for List")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []domain.LearningCategory
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) ([]domain.LearningCategory, error)); ok {
|
||||||
|
return rf(_a0)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) []domain.LearningCategory); ok {
|
||||||
|
r0 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]domain.LearningCategory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||||
|
r1 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// LearningCategoryRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
|
||||||
|
type LearningCategoryRepository_List_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
func (_e *LearningCategoryRepository_Expecter) List(_a0 interface{}) *LearningCategoryRepository_List_Call {
|
||||||
|
return &LearningCategoryRepository_List_Call{Call: _e.mock.On("List", _a0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_List_Call) Run(run func(_a0 context.Context)) *LearningCategoryRepository_List_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_List_Call) Return(_a0 []domain.LearningCategory, _a1 error) *LearningCategoryRepository_List_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_List_Call) RunAndReturn(run func(context.Context) ([]domain.LearningCategory, error)) *LearningCategoryRepository_List_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *LearningCategoryRepository) Upsert(_a0 context.Context, _a1 domain.LearningCategory) error {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Upsert")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.LearningCategory) error); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LearningCategoryRepository_Upsert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Upsert'
|
||||||
|
type LearningCategoryRepository_Upsert_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 domain.LearningCategory
|
||||||
|
func (_e *LearningCategoryRepository_Expecter) Upsert(_a0 interface{}, _a1 interface{}) *LearningCategoryRepository_Upsert_Call {
|
||||||
|
return &LearningCategoryRepository_Upsert_Call{Call: _e.mock.On("Upsert", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_Upsert_Call) Run(run func(_a0 context.Context, _a1 domain.LearningCategory)) *LearningCategoryRepository_Upsert_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(domain.LearningCategory))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_Upsert_Call) Return(_a0 error) *LearningCategoryRepository_Upsert_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *LearningCategoryRepository_Upsert_Call) RunAndReturn(run func(context.Context, domain.LearningCategory) error) *LearningCategoryRepository_Upsert_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLearningCategoryRepository creates a new instance of LearningCategoryRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewLearningCategoryRepository(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *LearningCategoryRepository {
|
||||||
|
mock := &LearningCategoryRepository{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
256
internal/kurious/domain/mocks/OrganizationRepository.go
Normal file
256
internal/kurious/domain/mocks/OrganizationRepository.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
domain "git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrganizationRepository is an autogenerated mock type for the OrganizationRepository type
|
||||||
|
type OrganizationRepository struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrganizationRepository_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *OrganizationRepository) EXPECT() *OrganizationRepository_Expecter {
|
||||||
|
return &OrganizationRepository_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *OrganizationRepository) Create(_a0 context.Context, _a1 domain.CreateOrganizationParams) (domain.Organization, error) {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Create")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.Organization
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.CreateOrganizationParams) (domain.Organization, error)); ok {
|
||||||
|
return rf(_a0, _a1)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.CreateOrganizationParams) domain.Organization); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.Organization)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, domain.CreateOrganizationParams) error); ok {
|
||||||
|
r1 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrganizationRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||||
|
type OrganizationRepository_Create_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 domain.CreateOrganizationParams
|
||||||
|
func (_e *OrganizationRepository_Expecter) Create(_a0 interface{}, _a1 interface{}) *OrganizationRepository_Create_Call {
|
||||||
|
return &OrganizationRepository_Create_Call{Call: _e.mock.On("Create", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Create_Call) Run(run func(_a0 context.Context, _a1 domain.CreateOrganizationParams)) *OrganizationRepository_Create_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(domain.CreateOrganizationParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Create_Call) Return(_a0 domain.Organization, _a1 error) *OrganizationRepository_Create_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Create_Call) RunAndReturn(run func(context.Context, domain.CreateOrganizationParams) (domain.Organization, error)) *OrganizationRepository_Create_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete provides a mock function with given fields: ctx, id
|
||||||
|
func (_m *OrganizationRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
ret := _m.Called(ctx, id)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrganizationRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
|
||||||
|
type OrganizationRepository_Delete_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
func (_e *OrganizationRepository_Expecter) Delete(ctx interface{}, id interface{}) *OrganizationRepository_Delete_Call {
|
||||||
|
return &OrganizationRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Delete_Call) Run(run func(ctx context.Context, id string)) *OrganizationRepository_Delete_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Delete_Call) Return(_a0 error) *OrganizationRepository_Delete_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Delete_Call) RunAndReturn(run func(context.Context, string) error) *OrganizationRepository_Delete_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *OrganizationRepository) Get(_a0 context.Context, _a1 domain.GetOrganizationParams) (domain.Organization, error) {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Get")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 domain.Organization
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.GetOrganizationParams) (domain.Organization, error)); ok {
|
||||||
|
return rf(_a0, _a1)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, domain.GetOrganizationParams) domain.Organization); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(domain.Organization)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, domain.GetOrganizationParams) error); ok {
|
||||||
|
r1 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrganizationRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||||
|
type OrganizationRepository_Get_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
// - _a1 domain.GetOrganizationParams
|
||||||
|
func (_e *OrganizationRepository_Expecter) Get(_a0 interface{}, _a1 interface{}) *OrganizationRepository_Get_Call {
|
||||||
|
return &OrganizationRepository_Get_Call{Call: _e.mock.On("Get", _a0, _a1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Get_Call) Run(run func(_a0 context.Context, _a1 domain.GetOrganizationParams)) *OrganizationRepository_Get_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(domain.GetOrganizationParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Get_Call) Return(_a0 domain.Organization, _a1 error) *OrganizationRepository_Get_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_Get_Call) RunAndReturn(run func(context.Context, domain.GetOrganizationParams) (domain.Organization, error)) *OrganizationRepository_Get_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// List provides a mock function with given fields: _a0
|
||||||
|
func (_m *OrganizationRepository) List(_a0 context.Context) ([]domain.Organization, error) {
|
||||||
|
ret := _m.Called(_a0)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for List")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []domain.Organization
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) ([]domain.Organization, error)); ok {
|
||||||
|
return rf(_a0)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) []domain.Organization); ok {
|
||||||
|
r0 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]domain.Organization)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||||
|
r1 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrganizationRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
|
||||||
|
type OrganizationRepository_List_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is a helper method to define mock.On call
|
||||||
|
// - _a0 context.Context
|
||||||
|
func (_e *OrganizationRepository_Expecter) List(_a0 interface{}) *OrganizationRepository_List_Call {
|
||||||
|
return &OrganizationRepository_List_Call{Call: _e.mock.On("List", _a0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_List_Call) Run(run func(_a0 context.Context)) *OrganizationRepository_List_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_List_Call) Return(_a0 []domain.Organization, _a1 error) *OrganizationRepository_List_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *OrganizationRepository_List_Call) RunAndReturn(run func(context.Context) ([]domain.Organization, error)) *OrganizationRepository_List_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrganizationRepository creates a new instance of OrganizationRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
// The first argument is typically a *testing.T value.
|
||||||
|
func NewOrganizationRepository(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *OrganizationRepository {
|
||||||
|
mock := &OrganizationRepository{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
@ -20,3 +20,11 @@ type Organization struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt nullable.Value[time.Time]
|
DeletedAt nullable.Value[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrganizationStat struct {
|
||||||
|
ID string
|
||||||
|
ExternalID nullable.Value[string]
|
||||||
|
Name string
|
||||||
|
|
||||||
|
CoursesCount uint64
|
||||||
|
}
|
||||||
|
|||||||
@ -12,8 +12,16 @@ type ListCoursesParams struct {
|
|||||||
CourseThematic string
|
CourseThematic string
|
||||||
OrganizationID string
|
OrganizationID string
|
||||||
|
|
||||||
NextPageToken string
|
|
||||||
Limit int
|
Limit int
|
||||||
|
Offset int
|
||||||
|
OrderBy string
|
||||||
|
Ascending bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListStatisticsParams struct {
|
||||||
|
LearningTypeID string
|
||||||
|
CourseThematicID string
|
||||||
|
OrganizaitonID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateCourseParams struct {
|
type CreateCourseParams struct {
|
||||||
@ -34,9 +42,19 @@ type CreateCourseParams struct {
|
|||||||
StartsAt time.Time
|
StartsAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LearningTypeStat struct {
|
||||||
|
Count int
|
||||||
|
CourseThematic map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListCoursesStatsResult struct {
|
||||||
|
StatsByLearningType map[string]LearningTypeStat
|
||||||
|
}
|
||||||
|
|
||||||
type ListCoursesResult struct {
|
type ListCoursesResult struct {
|
||||||
Courses []Course
|
Courses []Course
|
||||||
NextPageToken string
|
NextPageToken string
|
||||||
|
Count int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListLearningTypeResult struct {
|
type ListLearningTypeResult struct {
|
||||||
@ -51,12 +69,24 @@ type ListCourseThematicsResult struct {
|
|||||||
CourseThematicIDs []string
|
CourseThematicIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StatisticUnit struct {
|
||||||
|
LearningTypeID string
|
||||||
|
CourseThematicID string
|
||||||
|
OrganizationID string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListStatisticsResult struct {
|
||||||
|
LearningTypeStatistics []StatisticUnit
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate mockery --name CourseRepository
|
//go:generate mockery --name CourseRepository
|
||||||
type CourseRepository interface {
|
type CourseRepository interface {
|
||||||
// List courses by specifid parameters.
|
// List courses by specifid parameters.
|
||||||
List(context.Context, ListCoursesParams) (ListCoursesResult, error)
|
List(context.Context, ListCoursesParams) (ListCoursesResult, error)
|
||||||
ListLearningTypes(context.Context) (ListLearningTypeResult, error)
|
ListLearningTypes(context.Context) (ListLearningTypeResult, error)
|
||||||
ListCourseThematics(context.Context, ListCourseThematicsParams) (ListCourseThematicsResult, error)
|
ListCourseThematics(context.Context, ListCourseThematicsParams) (ListCourseThematicsResult, error)
|
||||||
|
ListStatistics(context.Context, ListStatisticsParams) (ListStatisticsResult, error)
|
||||||
// Get course by id.
|
// Get course by id.
|
||||||
// Should return ErrNotFound in case course not found.
|
// Should return ErrNotFound in case course not found.
|
||||||
Get(ctx context.Context, id string) (Course, error)
|
Get(ctx context.Context, id string) (Course, error)
|
||||||
@ -75,6 +105,11 @@ type CourseRepository interface {
|
|||||||
UpdateCourseDescription(ctx context.Context, id string, description string) error
|
UpdateCourseDescription(ctx context.Context, id string, description string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetOrganizationParams struct {
|
||||||
|
ID nullable.Value[string]
|
||||||
|
ExternalID nullable.Value[string]
|
||||||
|
}
|
||||||
|
|
||||||
type CreateOrganizationParams struct {
|
type CreateOrganizationParams struct {
|
||||||
ID string
|
ID string
|
||||||
ExternalID nullable.Value[string]
|
ExternalID nullable.Value[string]
|
||||||
@ -85,9 +120,59 @@ type CreateOrganizationParams struct {
|
|||||||
LogoLink string
|
LogoLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListOrganizationsParams struct {
|
||||||
|
LearningTypeID string
|
||||||
|
CourseThematicID string
|
||||||
|
IDs []string
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate mockery --name OrganizationRepository
|
//go:generate mockery --name OrganizationRepository
|
||||||
type OrganizationRepository interface {
|
type OrganizationRepository interface {
|
||||||
Get(ctx context.Context) (Organization, error)
|
ListStats(context.Context, ListOrganizationsParams) ([]OrganizationStat, error)
|
||||||
|
List(context.Context, ListOrganizationsParams) ([]Organization, error)
|
||||||
|
Get(context.Context, GetOrganizationParams) (Organization, error)
|
||||||
Create(context.Context, CreateOrganizationParams) (Organization, error)
|
Create(context.Context, CreateOrganizationParams) (Organization, error)
|
||||||
Delete(ctx context.Context, id string) error
|
Delete(ctx context.Context, id string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotImplementedOrganizationRepository struct{}
|
||||||
|
|
||||||
|
func (NotImplementedOrganizationRepository) ListStats(
|
||||||
|
context.Context,
|
||||||
|
ListOrganizationsParams,
|
||||||
|
) ([]OrganizationStat, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NotImplementedOrganizationRepository) List(context.Context, ListOrganizationsParams) ([]Organization, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (NotImplementedOrganizationRepository) Get(context.Context, GetOrganizationParams) (Organization, error) {
|
||||||
|
return Organization{}, ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (NotImplementedOrganizationRepository) Create(context.Context, CreateOrganizationParams) (Organization, error) {
|
||||||
|
return Organization{}, ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (NotImplementedOrganizationRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate mockery --name LearningCategoryRepository
|
||||||
|
type LearningCategoryRepository interface {
|
||||||
|
Upsert(context.Context, LearningCategory) error
|
||||||
|
|
||||||
|
List(context.Context) ([]LearningCategory, error)
|
||||||
|
Get(context.Context, string) (LearningCategory, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotImplementedLearningCategory struct{}
|
||||||
|
|
||||||
|
func (NotImplementedLearningCategory) Upsert(context.Context, LearningCategory) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (NotImplementedLearningCategory) List(context.Context) ([]LearningCategory, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (NotImplementedLearningCategory) Get(context.Context, string) (LearningCategory, error) {
|
||||||
|
return LearningCategory{}, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ type syncSravniHandler struct {
|
|||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
|
|
||||||
knownExternalIDs map[string]struct{}
|
knownExternalIDs map[string]struct{}
|
||||||
|
knownOrganizationsByExternalID map[string]struct{}
|
||||||
isRunning uint32
|
isRunning uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
|
|||||||
learningTypes := state.Props.InitialReduxState.Dictionaries.Data.LearningType
|
learningTypes := state.Props.InitialReduxState.Dictionaries.Data.LearningType
|
||||||
courses := make([]sravni.Course, 0, 1024)
|
courses := make([]sravni.Course, 0, 1024)
|
||||||
buffer := make([]sravni.Course, 0, 512)
|
buffer := make([]sravni.Course, 0, 512)
|
||||||
|
organizations := make([]sravni.Organization, 0, 256)
|
||||||
for _, learningType := range learningTypes.Fields {
|
for _, learningType := range learningTypes.Fields {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@ -115,61 +117,98 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
// since count is known it might be optimized to allocate slice once per request.
|
// since count is known it might be optimized to allocate slice once per request.
|
||||||
for _, courseThematic := range thematics {
|
for _, courseThematic := range thematics {
|
||||||
buffer = buffer[:0]
|
var filteredCourses int
|
||||||
buffer, err = h.loadEducationalProducts(lctx, learningType.Value, courseThematic, buffer)
|
var filteredOrgs int
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, context.Canceled) {
|
|
||||||
xcontext.LogWithWarnError(lctx, h.log, err, "unable to load educational products", slog.Int("count", len(thematics)))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var orgsByID map[string]sravni.Organization
|
||||||
|
buffer = buffer[:0]
|
||||||
|
buffer, orgsByID, err = h.loadEducationalProducts(lctx, learningType.Value, courseThematic, buffer)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
return fmt.Errorf("loading educational products: %w", err)
|
return fmt.Errorf("loading educational products: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xcontext.LogWithWarnError(lctx, h.log, err, "unable to load educational products", slog.Int("count", len(thematics)))
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
xslices.ForEach(buffer, func(c sravni.Course) {
|
xslices.ForEach(buffer, func(c sravni.Course) {
|
||||||
|
// TODO: if the same course appears in different categories, it should be handled
|
||||||
|
if !h.setCourseIfNotKnown(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Learningtype = []string{learningType.Value}
|
c.Learningtype = []string{learningType.Value}
|
||||||
c.CourseThematics = []string{courseThematic}
|
c.CourseThematics = []string{courseThematic}
|
||||||
courses = append(courses, c)
|
courses = append(courses, c)
|
||||||
|
filteredCourses++
|
||||||
})
|
})
|
||||||
|
|
||||||
xcontext.LogInfo(lctx, h.log, "parsed subitems", slog.String("course_thematic", courseThematic), slog.Int("amount", len(buffer)))
|
for _, org := range orgsByID {
|
||||||
|
if !h.setOrganizationIfNotKnown(org) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
organizations = append(organizations, org)
|
||||||
|
filteredOrgs++
|
||||||
|
}
|
||||||
|
|
||||||
|
xcontext.LogInfo(
|
||||||
|
lctx, h.log, "parsed subitems",
|
||||||
|
slog.String("course_thematic", courseThematic),
|
||||||
|
slog.Int("amount", len(buffer)),
|
||||||
|
slog.Int("new_courses", filteredCourses),
|
||||||
|
slog.Int("new_organizations", filteredOrgs),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
xcontext.LogDebug(lctx, h.log, "parsed items", slog.Duration("elapsed", elapsed), slog.Int("amount", len(courses)))
|
xcontext.LogDebug(lctx, h.log, "parsed items", slog.Duration("elapsed", elapsed), slog.Int("amount", len(courses)))
|
||||||
|
|
||||||
// TODO: if the same course appears in different categories, it should be handled
|
xcontext.LogDebug(
|
||||||
courses = h.filterByCache(courses)
|
lctx, h.log, "filtered items",
|
||||||
if len(courses) == 0 {
|
slog.Int("courses", len(courses)),
|
||||||
xcontext.LogInfo(lctx, h.log, "all courses were filtered out")
|
slog.Int("organizations", len(organizations)),
|
||||||
continue
|
)
|
||||||
|
|
||||||
|
var insertCourseSuccess bool
|
||||||
|
if len(courses) > 0 {
|
||||||
|
err = h.insertCourses(lctx, courses)
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogWithError(lctx, h.log, err, "unable to insert courses")
|
||||||
}
|
}
|
||||||
|
|
||||||
xcontext.LogDebug(lctx, h.log, "filtered items", slog.Int("amount", len(courses)))
|
insertCourseSuccess = err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var insertOrgsSuccess bool
|
||||||
|
if len(organizations) > 0 {
|
||||||
|
err = h.insertOrganizations(lctx, organizations)
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogWithError(lctx, h.log, err, "unable to insert courses")
|
||||||
|
}
|
||||||
|
|
||||||
|
insertOrgsSuccess = err == nil
|
||||||
|
}
|
||||||
|
|
||||||
err = h.insertValues(lctx, courses)
|
|
||||||
elapsed = time.Since(start) - elapsed
|
elapsed = time.Since(start) - elapsed
|
||||||
elapsedField := slog.Duration("elapsed", elapsed)
|
elapsedField := slog.Duration("elapsed", elapsed)
|
||||||
if err != nil {
|
|
||||||
xcontext.LogWithError(lctx, h.log, err, "unable to insert courses", elapsedField)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
xslices.ForEach(courses, func(c sravni.Course) {
|
|
||||||
h.knownExternalIDs[c.ID] = struct{}{}
|
|
||||||
})
|
|
||||||
|
|
||||||
xcontext.LogInfo(
|
xcontext.LogInfo(
|
||||||
lctx, h.log, "processed items",
|
lctx, h.log, "inserting finished",
|
||||||
elapsedField,
|
elapsedField,
|
||||||
slog.Int("count", len(courses)),
|
slog.Bool("courses_insert_success", insertCourseSuccess),
|
||||||
|
slog.Bool("organization_insert_success", insertOrgsSuccess),
|
||||||
|
slog.Int("courses_count", len(courses)),
|
||||||
|
slog.Int("organizations_count", len(organizations)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *syncSravniHandler) loadEducationalProducts(ctx context.Context, learningType, courseThematic string, buf []sravni.Course) ([]sravni.Course, error) {
|
func (h *syncSravniHandler) loadEducationalProducts(ctx context.Context, learningType, courseThematic string, buf []sravni.Course) ([]sravni.Course, map[string]sravni.Organization, error) {
|
||||||
const maxDeepIteration = 10
|
const maxDeepIteration = 10
|
||||||
const defaultLimit = 50
|
const defaultLimit = 50
|
||||||
|
|
||||||
@ -177,6 +216,7 @@ func (h *syncSravniHandler) loadEducationalProducts(ctx context.Context, learnin
|
|||||||
rateLimit := rate.NewLimiter(rateStrategy, 1)
|
rateLimit := rate.NewLimiter(rateStrategy, 1)
|
||||||
|
|
||||||
var courses []sravni.Course
|
var courses []sravni.Course
|
||||||
|
var organizationsByID = make(map[string]sravni.Organization)
|
||||||
if buf == nil || cap(buf) == 0 {
|
if buf == nil || cap(buf) == 0 {
|
||||||
courses = make([]sravni.Course, 0, 256)
|
courses = make([]sravni.Course, 0, 256)
|
||||||
} else {
|
} else {
|
||||||
@ -193,35 +233,49 @@ func (h *syncSravniHandler) loadEducationalProducts(ctx context.Context, learnin
|
|||||||
params.Offset = offset
|
params.Offset = offset
|
||||||
response, err := h.client.ListEducationalProducts(ctx, params)
|
response, err := h.client.ListEducationalProducts(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("listing educational products: %w", err)
|
return nil, nil, fmt.Errorf("listing educational products: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += defaultLimit
|
offset += defaultLimit
|
||||||
|
|
||||||
courses = append(courses, response.Items...)
|
courses = append(courses, response.Items...)
|
||||||
|
|
||||||
|
for oid, org := range response.Organizations {
|
||||||
|
organizationsByID[oid] = org
|
||||||
|
}
|
||||||
|
|
||||||
if len(response.Items) < defaultLimit {
|
if len(response.Items) < defaultLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rateLimit.Wait(ctx)
|
err = rateLimit.Wait(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return courses, fmt.Errorf("waiting for limit: %w", err)
|
return courses, organizationsByID, fmt.Errorf("waiting for limit: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return courses, nil
|
return courses, organizationsByID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *syncSravniHandler) filterByCache(courses []sravni.Course) (toInsert []sravni.Course) {
|
func (h *syncSravniHandler) setCourseIfNotKnown(course sravni.Course) (set bool) {
|
||||||
toCut := xslices.FilterInplace(courses, xslices.Not(h.isCached))
|
|
||||||
return courses[:toCut]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *syncSravniHandler) isCached(course sravni.Course) bool {
|
|
||||||
_, ok := h.knownExternalIDs[course.ID]
|
_, ok := h.knownExternalIDs[course.ID]
|
||||||
return ok
|
if !ok {
|
||||||
|
h.knownExternalIDs[course.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *syncSravniHandler) insertValues(ctx context.Context, courses []sravni.Course) error {
|
return !ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *syncSravniHandler) setOrganizationIfNotKnown(organization sravni.Organization) bool {
|
||||||
|
_, ok := h.knownOrganizationsByExternalID[organization.ID]
|
||||||
|
if !ok {
|
||||||
|
h.knownOrganizationsByExternalID[organization.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *syncSravniHandler) insertCourses(ctx context.Context, courses []sravni.Course) error {
|
||||||
courseParams := xslices.Map(courses, courseAsCreateCourseParams)
|
courseParams := xslices.Map(courses, courseAsCreateCourseParams)
|
||||||
err := h.svc.Commands.InsertCourses.Handle(ctx, command.CreateCourses{
|
err := h.svc.Commands.InsertCourses.Handle(ctx, command.CreateCourses{
|
||||||
Courses: courseParams,
|
Courses: courseParams,
|
||||||
@ -233,7 +287,69 @@ func (h *syncSravniHandler) insertValues(ctx context.Context, courses []sravni.C
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *syncSravniHandler) insertOrganizations(ctx context.Context, organizations []sravni.Organization) error {
|
||||||
|
organizationParams := xslices.Map(organizations, func(in sravni.Organization) command.CreateOrganization {
|
||||||
|
return command.CreateOrganization{
|
||||||
|
ID: generator.RandomInt64ID(),
|
||||||
|
ExternalID: nullable.NewValue(in.ID),
|
||||||
|
Alias: in.Alias,
|
||||||
|
Name: in.Name.Short,
|
||||||
|
Site: "",
|
||||||
|
Logo: in.Logotypes.Web,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, params := range organizationParams {
|
||||||
|
err := h.svc.Commands.InsertOrganization.Handle(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inserting organization: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *syncSravniHandler) fillOrganizaionCaches(ctx context.Context) error {
|
||||||
|
if h.knownOrganizationsByExternalID != nil {
|
||||||
|
xcontext.LogDebug(ctx, h.log, "organization cache already filled")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
organizations, err := h.svc.Queries.ListOrganzations.Handle(ctx, query.ListOrganizations{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing organizations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
withExternalID := func(in domain.Organization) bool {
|
||||||
|
return in.ExternalID.Valid()
|
||||||
|
}
|
||||||
|
getExtID := func(in domain.Organization) string {
|
||||||
|
return in.ExternalID.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
h.knownOrganizationsByExternalID = xslices.AsMap(xslices.Filter(organizations, withExternalID), getExtID)
|
||||||
|
|
||||||
|
xcontext.LogInfo(ctx, h.log, "cache filled", slog.String("kind", "organizations_by_external_id"), slog.Int("count", len(organizations)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *syncSravniHandler) fillCaches(ctx context.Context) error {
|
func (h *syncSravniHandler) fillCaches(ctx context.Context) error {
|
||||||
|
err := h.fillOrganizaionCaches(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.fillKnownExternalIDsCache(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *syncSravniHandler) fillKnownExternalIDsCache(ctx context.Context) error {
|
||||||
if h.knownExternalIDs != nil {
|
if h.knownExternalIDs != nil {
|
||||||
xcontext.LogInfo(ctx, h.log, "cache already filled")
|
xcontext.LogInfo(ctx, h.log, "cache already filled")
|
||||||
|
|
||||||
@ -256,7 +372,7 @@ func (h *syncSravniHandler) fillCaches(ctx context.Context) error {
|
|||||||
h.knownExternalIDs[c.ExternalID.Value()] = struct{}{}
|
h.knownExternalIDs[c.ExternalID.Value()] = struct{}{}
|
||||||
})
|
})
|
||||||
|
|
||||||
xcontext.LogInfo(ctx, h.log, "cache filled", slog.Int("count", len(courses)))
|
xcontext.LogInfo(ctx, h.log, "cache filled", slog.String("kind", "courses_by_external_id"), slog.Int("count", len(courses)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package templ
|
package bootstrap
|
||||||
|
|
||||||
templ button(title string, attributes templ.Attributes) {
|
templ button(title string, attributes templ.Attributes) {
|
||||||
<button class="button" { attributes... }>{ title }</button>
|
<button class="button" { attributes... }>{ title }</button>
|
||||||
@ -11,7 +11,6 @@ templ buttonRedirect(id, title string, linkTo string) {
|
|||||||
>
|
>
|
||||||
{ title }
|
{ title }
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@onclickRedirect("origin-link-" + id, linkTo)
|
@onclickRedirect("origin-link-" + id, linkTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
// templ: version: v0.2.513
|
// templ: version: v0.2.707
|
||||||
package templ
|
package bootstrap
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ func button(title string, attributes templ.Attributes) templ.Component {
|
|||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/common.templ`, Line: 3, Col: 49}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/common.templ`, Line: 4, Col: 49}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@ -72,7 +72,12 @@ func buttonRedirect(id, title string, linkTo string) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("origin-link-" + id))
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("origin-link-" + id)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/common.templ`, Line: 10, Col: 26}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@ -80,12 +85,12 @@ func buttonRedirect(id, title string, linkTo string) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var4 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/common.templ`, Line: 11, Col: 8}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/common.templ`, Line: 12, Col: 9}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@ -106,11 +111,12 @@ func buttonRedirect(id, title string, linkTo string) templ.Component {
|
|||||||
|
|
||||||
func onclickRedirect(id, to string) templ.ComponentScript {
|
func onclickRedirect(id, to string) templ.ComponentScript {
|
||||||
return templ.ComponentScript{
|
return templ.ComponentScript{
|
||||||
Name: `__templ_onclickRedirect_47ae`,
|
Name: `__templ_onclickRedirect_5c43`,
|
||||||
Function: `function __templ_onclickRedirect_47ae(id, to){document.getElementById(id).onclick = () => {
|
Function: `function __templ_onclickRedirect_5c43(id, to){document.getElementById(id).onclick = () => {
|
||||||
location.href = to
|
location.href = to
|
||||||
}}`,
|
}
|
||||||
Call: templ.SafeScript(`__templ_onclickRedirect_47ae`, id, to),
|
}`,
|
||||||
CallInline: templ.SafeScriptInline(`__templ_onclickRedirect_47ae`, id, to),
|
Call: templ.SafeScript(`__templ_onclickRedirect_5c43`, id, to),
|
||||||
|
CallInline: templ.SafeScriptInline(`__templ_onclickRedirect_5c43`, id, to),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
236
internal/kurious/ports/http/bootstrap/core.templ
Normal file
236
internal/kurious/ports/http/bootstrap/core.templ
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
templ head(title string) {
|
||||||
|
<head>
|
||||||
|
<title>{ title }</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||||
|
/>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ headerNavbar(page PageKind) {
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary w-auto">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/htmlexamples/index.html">Kurious</a>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent"
|
||||||
|
aria-controls="navbarSupportedContent"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
|
class={ "nav-link", templ.KV("active", page == PageIndex) }
|
||||||
|
aria-current="page"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
|
class={ "nav-link", templ.KV("active", page == PageCourses) }
|
||||||
|
aria-current="page"
|
||||||
|
href="/courses"
|
||||||
|
>
|
||||||
|
Courses
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
|
class={ "nav-link", templ.KV("active", page == PageAbout) }
|
||||||
|
href="/about"
|
||||||
|
>
|
||||||
|
About us
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ footer() {
|
||||||
|
<footer class="text-center text-lg-start bg-body-tertiary text-muted">
|
||||||
|
<section class="p-2">
|
||||||
|
<div class="container text-center text-md-start mt-5">
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-3 col-lg-4 col-xl-3 mx-auto mb-4">
|
||||||
|
<h6 class="text-uppercase fw-bold mb-4">
|
||||||
|
<i class="fas fa-gem me-3"></i>Courses
|
||||||
|
</h6>
|
||||||
|
<p>
|
||||||
|
Welcome to Courses, your gateway to learning! Explore a diverse
|
||||||
|
range of courses and advance your skills with us. Join our
|
||||||
|
community and transform your life through education.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 col-lg-2 col-xl-2 mx-auto mb-4">
|
||||||
|
<h6 class="text-uppercase fw-bold mb-4">Useful links</h6>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Pricing</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Settings</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Orders</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="#!" class="text-reset">Help</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-lg-3 col-xl-3 mx-auto mb-md-0 mb-4">
|
||||||
|
<h6 class="text-uppercase fw-bold mb-4">Contact</h6>
|
||||||
|
<p><i class="fas fa-home me-3"></i> New York, NY 10012, US</p>
|
||||||
|
<p>
|
||||||
|
<i class="fas fa-envelope me-3"></i>
|
||||||
|
info@example.com
|
||||||
|
</p>
|
||||||
|
<p><i class="fas fa-phone me-3"></i> + 01 234 567 88</p>
|
||||||
|
<p><i class="fas fa-print me-3"></i> + 01 234 567 89</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="text-center p-4" style="background-color: rgba(0, 0, 0, 0.05)">
|
||||||
|
© 2024 Copyright:
|
||||||
|
<a class="text-reset fw-bold" href="https://mdbootstrap.com/">kursov.net</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
}
|
||||||
|
|
||||||
|
script elementScriptsLoad() {
|
||||||
|
const loadInputValues = () => {
|
||||||
|
const lt = document.getElementById('learning-type-filter');
|
||||||
|
const ct = document.getElementById('course-thematic-filter');
|
||||||
|
|
||||||
|
const prefix = (lt !== null && lt.value !== '') ? `/courses/${lt.value}` : `/courses`;
|
||||||
|
const base_url = (ct !== null && ct.value !== '') ? `${prefix}/${ct.value}` : prefix;
|
||||||
|
|
||||||
|
const school_selector = document.getElementById('schoolSelect');
|
||||||
|
const school_id = (school_selector !== null && school_selector.value !== '') ? school_selector.value : '';
|
||||||
|
|
||||||
|
const order_by = document.getElementById('sortBySelect');
|
||||||
|
const order_by_value = (order_by !== null && order_by.value !== '') ? order_by.value : '';
|
||||||
|
|
||||||
|
const ascending = document.getElementById('sortByOrder');
|
||||||
|
|
||||||
|
params = [];
|
||||||
|
if (school_id) {
|
||||||
|
params.push(`school_id=${school_id}`);
|
||||||
|
};
|
||||||
|
if (order_by_value) {
|
||||||
|
params.push(`order_by=${order_by_value}`);
|
||||||
|
};
|
||||||
|
if (ascending && ascending.checked) {
|
||||||
|
params.push(`asc=true`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const final_url = base_url + "?" + params.join("&");
|
||||||
|
return {
|
||||||
|
base_url: base_url,
|
||||||
|
final_url: final_url,
|
||||||
|
school_id: school_id,
|
||||||
|
order_by: order_by_value,
|
||||||
|
ascending: ascending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formFilterOnSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// const lt = document.getElementById('learning-type-filter');
|
||||||
|
// const ct = document.getElementById('course-thematic-filter');
|
||||||
|
// const prefix = (lt !== null && lt.value !== '') ? `/courses/${lt.value}` : `/courses`;
|
||||||
|
// const out = (ct !== null && ct.value !== '') ? `${prefix}/${ct.value}` : prefix;
|
||||||
|
|
||||||
|
values = loadInputValues();
|
||||||
|
|
||||||
|
const out = values.final_url;
|
||||||
|
document.location.assign(out);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterBySchool = () => {
|
||||||
|
/*
|
||||||
|
const school_selector = document.getElementById('schoolSelect');
|
||||||
|
const school_id = (school_selector !== null && school_selector.value !== '') ? school_selector.value : '';
|
||||||
|
const order_by = document.getElementById('sortBySelect');
|
||||||
|
const order_by_value = (order_by !== null && order_by.value !== '') ? order_by.value : '';
|
||||||
|
const ascending = document.getElementById('sortByOrder');
|
||||||
|
|
||||||
|
const baseUrl = `${window.location.pathname}?`;
|
||||||
|
|
||||||
|
params = [];
|
||||||
|
if (school_id) {
|
||||||
|
params.push(`school_id=${school_id}`);
|
||||||
|
};
|
||||||
|
if (order_by_value) {
|
||||||
|
params.push(`order_by=${order_by_value}`);
|
||||||
|
};
|
||||||
|
if (ascending && ascending.checked) {
|
||||||
|
params.push(`asc=true`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalUrl = baseUrl + params.join("&");
|
||||||
|
*/
|
||||||
|
|
||||||
|
values = loadInputValues();
|
||||||
|
const finalUrl = values.final_url;
|
||||||
|
if (history.pushState) {
|
||||||
|
// history.pushState(null, null, finalUrl);
|
||||||
|
// TODO: remove once htmx implemented
|
||||||
|
window.location.assign(finalUrl);
|
||||||
|
} else {
|
||||||
|
window.location.assign(finalUrl);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const ff = document.getElementById('filter-form');
|
||||||
|
if (ff) ff.addEventListener('submit', formFilterOnSubmit);
|
||||||
|
|
||||||
|
const fs = document.getElementById('schoolSelect');
|
||||||
|
if (fs) fs.onchange = filterBySchool;
|
||||||
|
|
||||||
|
const ob = document.getElementById('sortBySelect');
|
||||||
|
if (ob) ob.onchange = filterBySchool;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
templ root(page PageKind, _ stats) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
@head(string(page))
|
||||||
|
<body>
|
||||||
|
@headerNavbar(page)
|
||||||
|
<div class="container">
|
||||||
|
{ children... }
|
||||||
|
</div>
|
||||||
|
@elementScriptsLoad()
|
||||||
|
</body>
|
||||||
|
@footer()
|
||||||
|
</html>
|
||||||
|
}
|
||||||
331
internal/kurious/ports/http/bootstrap/core_templ.go
Normal file
331
internal/kurious/ports/http/bootstrap/core_templ.go
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.2.707
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import "context"
|
||||||
|
import "io"
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
func head(title string) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><title>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/core.templ`, Line: 5, Col: 16}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\"><link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css\"><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL\" crossorigin=\"anonymous\">\n\t\t</script></head>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerNavbar(page PageKind) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var3 == nil {
|
||||||
|
templ_7745c5c3_Var3 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header><nav class=\"navbar navbar-expand-lg bg-body-tertiary w-auto\"><div class=\"container-fluid\"><a class=\"navbar-brand\" href=\"/htmlexamples/index.html\">Kurious</a> <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"><span class=\"navbar-toggler-icon\"></span></button><div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\"><ul class=\"navbar-nav me-auto mb-2 mb-lg-0\"><li class=\"nav-item\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 = []any{"nav-link", templ.KV("active", page == PageIndex)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/core.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" aria-current=\"page\" href=\"/\">Home</a></li><li class=\"nav-item\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 = []any{"nav-link", templ.KV("active", page == PageCourses)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/core.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" aria-current=\"page\" href=\"/courses\">Courses</a></li><li class=\"nav-item\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 = []any{"nav-link", templ.KV("active", page == PageAbout)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/core.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" href=\"/about\">About us</a></li></ul></div></div></nav></header>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func footer() templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var10 == nil {
|
||||||
|
templ_7745c5c3_Var10 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"text-center text-lg-start bg-body-tertiary text-muted\"><section class=\"p-2\"><div class=\"container text-center text-md-start mt-5\"><div class=\"row mt-3\"><div class=\"col-md-3 col-lg-4 col-xl-3 mx-auto mb-4\"><h6 class=\"text-uppercase fw-bold mb-4\"><i class=\"fas fa-gem me-3\"></i>Courses</h6><p>Welcome to Courses, your gateway to learning! Explore a diverse range of courses and advance your skills with us. Join our community and transform your life through education.</p></div><div class=\"col-md-3 col-lg-2 col-xl-2 mx-auto mb-4\"><h6 class=\"text-uppercase fw-bold mb-4\">Useful links</h6><p><a href=\"#!\" class=\"text-reset\">Pricing</a></p><p><a href=\"#!\" class=\"text-reset\">Settings</a></p><p><a href=\"#!\" class=\"text-reset\">Orders</a></p><p><a href=\"#!\" class=\"text-reset\">Help</a></p></div><div class=\"col-md-4 col-lg-3 col-xl-3 mx-auto mb-md-0 mb-4\"><h6 class=\"text-uppercase fw-bold mb-4\">Contact</h6><p><i class=\"fas fa-home me-3\"></i> New York, NY 10012, US</p><p><i class=\"fas fa-envelope me-3\"></i> info@example.com</p><p><i class=\"fas fa-phone me-3\"></i> + 01 234 567 88</p><p><i class=\"fas fa-print me-3\"></i> + 01 234 567 89</p></div></div></div></section><div class=\"text-center p-4\" style=\"background-color: rgba(0, 0, 0, 0.05)\">© 2024 Copyright: <a class=\"text-reset fw-bold\" href=\"https://mdbootstrap.com/\">kursov.net</a></div></footer>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func elementScriptsLoad() templ.ComponentScript {
|
||||||
|
return templ.ComponentScript{
|
||||||
|
Name: `__templ_elementScriptsLoad_bfb1`,
|
||||||
|
Function: `function __templ_elementScriptsLoad_bfb1(){const loadInputValues = () => {
|
||||||
|
const lt = document.getElementById('learning-type-filter');
|
||||||
|
const ct = document.getElementById('course-thematic-filter');
|
||||||
|
|
||||||
|
const prefix = (lt !== null && lt.value !== '') ? ` + "`" + `/courses/${lt.value}` + "`" + ` : ` + "`" + `/courses` + "`" + `;
|
||||||
|
const base_url = (ct !== null && ct.value !== '') ? ` + "`" + `${prefix}/${ct.value}` + "`" + ` : prefix;
|
||||||
|
|
||||||
|
const school_selector = document.getElementById('schoolSelect');
|
||||||
|
const school_id = (school_selector !== null && school_selector.value !== '') ? school_selector.value : '';
|
||||||
|
|
||||||
|
const order_by = document.getElementById('sortBySelect');
|
||||||
|
const order_by_value = (order_by !== null && order_by.value !== '') ? order_by.value : '';
|
||||||
|
|
||||||
|
const ascending = document.getElementById('sortByOrder');
|
||||||
|
|
||||||
|
params = [];
|
||||||
|
if (school_id) {
|
||||||
|
params.push(` + "`" + `school_id=${school_id}` + "`" + `);
|
||||||
|
};
|
||||||
|
if (order_by_value) {
|
||||||
|
params.push(` + "`" + `order_by=${order_by_value}` + "`" + `);
|
||||||
|
};
|
||||||
|
if (ascending && ascending.checked) {
|
||||||
|
params.push(` + "`" + `asc=true` + "`" + `);
|
||||||
|
};
|
||||||
|
|
||||||
|
const final_url = base_url + "?" + params.join("&");
|
||||||
|
return {
|
||||||
|
base_url: base_url,
|
||||||
|
final_url: final_url,
|
||||||
|
school_id: school_id,
|
||||||
|
order_by: order_by_value,
|
||||||
|
ascending: ascending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formFilterOnSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// const lt = document.getElementById('learning-type-filter');
|
||||||
|
// const ct = document.getElementById('course-thematic-filter');
|
||||||
|
// const prefix = (lt !== null && lt.value !== '') ? ` + "`" + `/courses/${lt.value}` + "`" + ` : ` + "`" + `/courses` + "`" + `;
|
||||||
|
// const out = (ct !== null && ct.value !== '') ? ` + "`" + `${prefix}/${ct.value}` + "`" + ` : prefix;
|
||||||
|
|
||||||
|
values = loadInputValues();
|
||||||
|
|
||||||
|
const out = values.final_url;
|
||||||
|
document.location.assign(out);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterBySchool = () => {
|
||||||
|
/*
|
||||||
|
const school_selector = document.getElementById('schoolSelect');
|
||||||
|
const school_id = (school_selector !== null && school_selector.value !== '') ? school_selector.value : '';
|
||||||
|
const order_by = document.getElementById('sortBySelect');
|
||||||
|
const order_by_value = (order_by !== null && order_by.value !== '') ? order_by.value : '';
|
||||||
|
const ascending = document.getElementById('sortByOrder');
|
||||||
|
|
||||||
|
const baseUrl = ` + "`" + `${window.location.pathname}?` + "`" + `;
|
||||||
|
|
||||||
|
params = [];
|
||||||
|
if (school_id) {
|
||||||
|
params.push(` + "`" + `school_id=${school_id}` + "`" + `);
|
||||||
|
};
|
||||||
|
if (order_by_value) {
|
||||||
|
params.push(` + "`" + `order_by=${order_by_value}` + "`" + `);
|
||||||
|
};
|
||||||
|
if (ascending && ascending.checked) {
|
||||||
|
params.push(` + "`" + `asc=true` + "`" + `);
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalUrl = baseUrl + params.join("&");
|
||||||
|
*/
|
||||||
|
|
||||||
|
values = loadInputValues();
|
||||||
|
const finalUrl = values.final_url;
|
||||||
|
if (history.pushState) {
|
||||||
|
// history.pushState(null, null, finalUrl);
|
||||||
|
// TODO: remove once htmx implemented
|
||||||
|
window.location.assign(finalUrl);
|
||||||
|
} else {
|
||||||
|
window.location.assign(finalUrl);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const ff = document.getElementById('filter-form');
|
||||||
|
if (ff) ff.addEventListener('submit', formFilterOnSubmit);
|
||||||
|
|
||||||
|
const fs = document.getElementById('schoolSelect');
|
||||||
|
if (fs) fs.onchange = filterBySchool;
|
||||||
|
|
||||||
|
const ob = document.getElementById('sortBySelect');
|
||||||
|
if (ob) ob.onchange = filterBySchool;
|
||||||
|
});
|
||||||
|
}`,
|
||||||
|
Call: templ.SafeScript(`__templ_elementScriptsLoad_bfb1`),
|
||||||
|
CallInline: templ.SafeScriptInline(`__templ_elementScriptsLoad_bfb1`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func root(page PageKind, _ stats) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var11 == nil {
|
||||||
|
templ_7745c5c3_Var11 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"ru\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = head(string(page)).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = headerNavbar(page).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"container\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_Var11.Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = elementScriptsLoad().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = footer().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
239
internal/kurious/ports/http/bootstrap/list.templ
Normal file
239
internal/kurious/ports/http/bootstrap/list.templ
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import "path"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
templ breadcrumbsItem(text, link string, isActive bool) {
|
||||||
|
<li class={ "breadcrumb-item", templ.KV("active", isActive) }>
|
||||||
|
if link != "" {
|
||||||
|
<a
|
||||||
|
href={ templ.URL(link) }
|
||||||
|
itemprop="url"
|
||||||
|
aria-label="breadcrumb"
|
||||||
|
>{ text }</a>
|
||||||
|
} else {
|
||||||
|
<span itemprop="url" itemprop="title">{ text }</span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ breadcrumNode(params BreadcrumbsParams) {
|
||||||
|
<nav
|
||||||
|
class={ "mt-4", breadcrumbSymbol() }
|
||||||
|
aria-label="breadcrumbs"
|
||||||
|
itemprop="breadcrumb"
|
||||||
|
itemtype="https://schema.org/BreadcrumbList"
|
||||||
|
itemscope
|
||||||
|
>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
@breadcrumbsItem("Курсы", "/courses", params.ActiveLearningType.Empty())
|
||||||
|
if !params.ActiveLearningType.Empty() {
|
||||||
|
@breadcrumbsItem(
|
||||||
|
params.ActiveLearningType.Name,
|
||||||
|
path.Join("/", "courses", params.ActiveLearningType.ID),
|
||||||
|
params.ActiveCourseThematic.Empty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !params.ActiveCourseThematic.Empty() {
|
||||||
|
@breadcrumbsItem(
|
||||||
|
params.ActiveCourseThematic.Name,
|
||||||
|
path.Join("/", "courses", params.ActiveLearningType.ID, params.ActiveCourseThematic.ID),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
|
||||||
|
css breadcrumbSymbol() {
|
||||||
|
--bs-breadcrumb-divider: "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
templ listCoursesSectionHeader(params BreadcrumbsParams) {
|
||||||
|
<section class="row bc-header">
|
||||||
|
@breadcrumNode(params)
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ listCoursesSectionFilters(params FilterFormParams) {
|
||||||
|
<section class="row filters">
|
||||||
|
<div
|
||||||
|
class={
|
||||||
|
templ.KV("visually-hidden", !params.Render),
|
||||||
|
"p-2",
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<form id="filter-form" class="input-group">
|
||||||
|
<span class="input-group-text">Filter courses</span>
|
||||||
|
<select
|
||||||
|
id="learning-type-filter"
|
||||||
|
class={ "form-select" }
|
||||||
|
>
|
||||||
|
<option value="" selected?={ params.ActiveLearningType.ID=="" }>All</option>
|
||||||
|
for _, learningType := range params.AvailableLearningTypes {
|
||||||
|
<option
|
||||||
|
selected?={ params.ActiveLearningType.ID==learningType.ID }
|
||||||
|
value={ learningType.ID }
|
||||||
|
>{ learningType.Name }</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
id="course-thematic-filter"
|
||||||
|
class={ "form-select", templ.KV("d-none", len(params.AvailableCourseThematics) == 0) }
|
||||||
|
>
|
||||||
|
<option value="" selected?={ params.ActiveLearningType.ID=="" }>All</option>
|
||||||
|
for _, courseThematic := range params.AvailableCourseThematics {
|
||||||
|
<option
|
||||||
|
selected?={ params.ActiveCourseThematic.ID==courseThematic.ID }
|
||||||
|
value={ courseThematic.ID }
|
||||||
|
>{ courseThematic.Name }</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<button id="filter-course-thematic" class="btn btn-outline-secondary" type="submit">Go</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@filterCoursesSelectView(params.Schools)
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ selectOptionFromPairs(emptyName, activeID string, items []NameIDPair) {
|
||||||
|
<option value="" selected?={ activeID == "" }>
|
||||||
|
{ emptyName }
|
||||||
|
</option>
|
||||||
|
for _, item := range items {
|
||||||
|
<option
|
||||||
|
selected?={ activeID==item.ID }
|
||||||
|
value={ item.ID }
|
||||||
|
>
|
||||||
|
{ item.Name }
|
||||||
|
</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ filterCoursesSelectView(params CoursesFilterViewParams) {
|
||||||
|
<div class="filter-content d-flex p-2">
|
||||||
|
<!-- School list -->
|
||||||
|
<div class="col-3">
|
||||||
|
<select name="school" id="schoolSelect" class="form-select" aria-label="school filter">
|
||||||
|
@selectOptionFromPairs("Pick a school:", params.SelectedSchoolID, params.Schools)
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<!-- Sort options -->
|
||||||
|
<div class="col-3 mx-4">
|
||||||
|
<div class="input-group flex-nowrap">
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="sortBySelect"
|
||||||
|
aria-label="Sorting items by requested order"
|
||||||
|
>
|
||||||
|
@selectOptionFromPairs("Sort by:", params.OrderBy, params.OrderFields)
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="btn-check"
|
||||||
|
name="sortByOrder"
|
||||||
|
id="sortByOrder"
|
||||||
|
autocomplete="off"
|
||||||
|
checked?={ !params.Ascending }
|
||||||
|
/>
|
||||||
|
<label class="btn" for="sortByOrder">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-sort-down"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3.5 2.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 11.293zm3.5 1a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M7.5 6a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4"></div>
|
||||||
|
<div class="col-auto ms-auto">
|
||||||
|
<div class="btn btn-primary">Promocodes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ listCoursesLearning(courses []CourseInfo) {
|
||||||
|
<div class="block">
|
||||||
|
<div class="row row-cols-1 row-cols-md-4 g-4">
|
||||||
|
for _, course := range courses {
|
||||||
|
@listCoursesCard(course)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ listCoursesLearningLegacy(containers []CategoryContainer) {
|
||||||
|
for _, container := range containers {
|
||||||
|
<section class="row first-class-group g-4">
|
||||||
|
<h1 class="title">{ container.Name }</h1>
|
||||||
|
for _, subcategory := range container.Subcategories {
|
||||||
|
@listCoursesThematicRowLegacy(container.ID, subcategory)
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ listCoursesThematicRowLegacy(categoryID string, subcategory SubcategoryContainer) {
|
||||||
|
<div class="block second-class-group">
|
||||||
|
<h3 class="title">
|
||||||
|
<a href={ templ.SafeURL("/courses/" + categoryID + "/" + subcategory.ID) } class="text-decoration-none">
|
||||||
|
{ subcategory.Name }
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-up-right-square" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm5.854 8.803a.5.5 0 1 1-.708-.707L9.243 6H6.475a.5.5 0 1 1 0-1h3.975a.5.5 0 0 1 .5.5v3.975a.5.5 0 1 1-1 0V6.707z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="visually-hidden">В категогрии { subcategory.Name } собраны { strconv.Itoa(subcategory.Count) } курсов. Раз в неделю мы обновляем информацию о всех курсах.</p>
|
||||||
|
<div class="row row-cols-1 row-cols-md-4 g-4">
|
||||||
|
for _, info := range subcategory.Courses {
|
||||||
|
@listCoursesCard(info)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
css myImg() {
|
||||||
|
min-height: 19rem;
|
||||||
|
min-width: 19rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
css cardTextSize() {
|
||||||
|
min-height: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
templ listCoursesCard(info CourseInfo) {
|
||||||
|
// <div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="col" id={ info.ID }>
|
||||||
|
<div class="card h-100">
|
||||||
|
<img src={ GetOrFallback(info.ImageLink, "https://placehold.co/128x128") } alt="Course picture" class={ "card-img-top" }/>
|
||||||
|
<div class={ "card-body", cardTextSize(), "row" }>
|
||||||
|
<h5 class="card-title">{ info.Name }</h5>
|
||||||
|
<div class="input-group d-flex align-self-end">
|
||||||
|
<a
|
||||||
|
href={ templ.URL(info.OriginLink) }
|
||||||
|
class="btn text btn-outline-primary flex-grow-1"
|
||||||
|
>Go!</a>
|
||||||
|
<span class="input-group-text justify-content-end flex-fill">
|
||||||
|
{ strconv.Itoa(info.FullPrice) } rub.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ListCourses(pageType PageKind, s stats, params ListCoursesParams) {
|
||||||
|
@root(pageType, s) {
|
||||||
|
@listCoursesSectionHeader(params.FilterForm.BreadcrumbsParams)
|
||||||
|
@listCoursesSectionFilters(params.FilterForm)
|
||||||
|
@listCoursesLearning(params.Courses)
|
||||||
|
@pagination(params.Pagination)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
templ listCoursesByCourseThematic(params ListCoursesParams) {
|
||||||
|
<div class="container">
|
||||||
|
<h2>Здесь вы можете найти интересующие вас курсы
|
||||||
|
по теме { params.FilterForm.ActiveLearningType.Name }:
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
for _, courseThematic := range params.FilterForm.AvailableCourseThematics {
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a href={templ.SafeURL("/courses/" + params.FilterForm.ActiveLearningType.ID + "/" + courseThematic.ID)}>
|
||||||
|
{courseThematic.Name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ListCourseThematics(pageType PageKind, s stats, params ListCoursesParams) {
|
||||||
|
@root(pageType, s) {
|
||||||
|
@listCoursesSectionHeader(params.FilterForm.BreadcrumbsParams)
|
||||||
|
@listCoursesSectionFilters(params.FilterForm)
|
||||||
|
@listCoursesByCourseThematic(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.2.707
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import "context"
|
||||||
|
import "io"
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
func listCoursesByCourseThematic(params ListCoursesParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"container\"><h2>Здесь вы можете найти интересующие вас курсы по теме ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(params.FilterForm.ActiveLearningType.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list_course_thematics.templ`, Line: 6, Col: 59}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(":</h2><ul class=\"list-group\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, courseThematic := range params.FilterForm.AvailableCourseThematics {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"list-group-item\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL("/courses/" + params.FilterForm.ActiveLearningType.ID + "/" + courseThematic.ID)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(courseThematic.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list_course_thematics.templ`, Line: 13, Col: 24}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListCourseThematics(pageType PageKind, s stats, params ListCoursesParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var5 == nil {
|
||||||
|
templ_7745c5c3_Var5 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var6 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesSectionHeader(params.FilterForm.BreadcrumbsParams).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesSectionFilters(params.FilterForm).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesByCourseThematic(params).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = root(pageType, s).Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
949
internal/kurious/ports/http/bootstrap/list_templ.go
Normal file
949
internal/kurious/ports/http/bootstrap/list_templ.go
Normal file
@ -0,0 +1,949 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.2.707
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import "context"
|
||||||
|
import "io"
|
||||||
|
import "bytes"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
import "path"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func breadcrumbsItem(text, link string, isActive bool) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
var templ_7745c5c3_Var2 = []any{"breadcrumb-item", templ.KV("active", isActive)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if link != "" {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(link)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" itemprop=\"url\" aria-label=\"breadcrumb\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 13, Col: 10}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span itemprop=\"url\" itemprop=\"title\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 15, Col: 47}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func breadcrumNode(params BreadcrumbsParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var7 == nil {
|
||||||
|
templ_7745c5c3_Var7 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
var templ_7745c5c3_Var8 = []any{"mt-4", breadcrumbSymbol()}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" aria-label=\"breadcrumbs\" itemprop=\"breadcrumb\" itemtype=\"https://schema.org/BreadcrumbList\" itemscope><ol class=\"breadcrumb\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = breadcrumbsItem("Курсы", "/courses", params.ActiveLearningType.Empty()).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !params.ActiveLearningType.Empty() {
|
||||||
|
templ_7745c5c3_Err = breadcrumbsItem(
|
||||||
|
params.ActiveLearningType.Name,
|
||||||
|
path.Join("/", "courses", params.ActiveLearningType.ID),
|
||||||
|
params.ActiveCourseThematic.Empty(),
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !params.ActiveCourseThematic.Empty() {
|
||||||
|
templ_7745c5c3_Err = breadcrumbsItem(
|
||||||
|
params.ActiveCourseThematic.Name,
|
||||||
|
path.Join("/", "courses", params.ActiveLearningType.ID, params.ActiveCourseThematic.ID),
|
||||||
|
true,
|
||||||
|
).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ol></nav>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func breadcrumbSymbol() templ.CSSClass {
|
||||||
|
var templ_7745c5c3_CSSBuilder strings.Builder
|
||||||
|
templ_7745c5c3_CSSBuilder.WriteString(`--bs-breadcrumb-divider:"/";`)
|
||||||
|
templ_7745c5c3_CSSID := templ.CSSID(`breadcrumbSymbol`, templ_7745c5c3_CSSBuilder.String())
|
||||||
|
return templ.ComponentCSSClass{
|
||||||
|
ID: templ_7745c5c3_CSSID,
|
||||||
|
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCoursesSectionHeader(params BreadcrumbsParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var10 == nil {
|
||||||
|
templ_7745c5c3_Var10 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section class=\"row bc-header\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = breadcrumNode(params).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCoursesSectionFilters(params FilterFormParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var11 == nil {
|
||||||
|
templ_7745c5c3_Var11 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section class=\"row filters\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var12 = []any{
|
||||||
|
templ.KV("visually-hidden", !params.Render),
|
||||||
|
"p-2",
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><form id=\"filter-form\" class=\"input-group\"><span class=\"input-group-text\">Filter courses</span> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var14 = []any{"form-select"}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<select id=\"learning-type-filter\" class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var15 string
|
||||||
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><option value=\"\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if params.ActiveLearningType.ID == "" {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">All</option> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, learningType := range params.AvailableLearningTypes {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if params.ActiveLearningType.ID == learningType.ID {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" value=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var16 string
|
||||||
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(learningType.ID)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 76, Col: 30}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var17 string
|
||||||
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(learningType.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 77, Col: 26}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var18 = []any{"form-select", templ.KV("d-none", len(params.AvailableCourseThematics) == 0)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<select id=\"course-thematic-filter\" class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var19 string
|
||||||
|
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var18).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><option value=\"\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if params.ActiveLearningType.ID == "" {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">All</option> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, courseThematic := range params.AvailableCourseThematics {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if params.ActiveCourseThematic.ID == courseThematic.ID {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" value=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var20 string
|
||||||
|
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(courseThematic.ID)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 88, Col: 32}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var21 string
|
||||||
|
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(courseThematic.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 89, Col: 28}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select> <button id=\"filter-course-thematic\" class=\"btn btn-outline-secondary\" type=\"submit\">Go</button></form></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = filterCoursesSelectView(params.Schools).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectOptionFromPairs(emptyName, activeID string, items []NameIDPair) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var22 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var22 == nil {
|
||||||
|
templ_7745c5c3_Var22 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if activeID == "" {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var23 string
|
||||||
|
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(emptyName)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 101, Col: 13}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if activeID == item.ID {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" value=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var24 string
|
||||||
|
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(item.ID)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 106, Col: 18}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var25 string
|
||||||
|
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 108, Col: 14}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCoursesSelectView(params CoursesFilterViewParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var26 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var26 == nil {
|
||||||
|
templ_7745c5c3_Var26 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"filter-content d-flex p-2\"><!-- School list --><div class=\"col-3\"><select name=\"school\" id=\"schoolSelect\" class=\"form-select\" aria-label=\"school filter\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = selectOptionFromPairs("Pick a school:", params.SelectedSchoolID, params.Schools).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select></div><!-- Sort options --><div class=\"col-3 mx-4\"><div class=\"input-group flex-nowrap\"><select class=\"form-select\" id=\"sortBySelect\" aria-label=\"Sorting items by requested order\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = selectOptionFromPairs("Sort by:", params.OrderBy, params.OrderFields).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select> <input type=\"radio\" class=\"btn-check\" name=\"sortByOrder\" id=\"sortByOrder\" autocomplete=\"off\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !params.Ascending {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" checked")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("> <label class=\"btn\" for=\"sortByOrder\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-sort-down\" viewBox=\"0 0 16 16\"><path d=\"M3.5 2.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 11.293zm3.5 1a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5M7.5 6a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1z\"></path></svg></label></div></div><div class=\"col-4\"></div><div class=\"col-auto ms-auto\"><div class=\"btn btn-primary\">Promocodes</div></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCoursesLearning(courses []CourseInfo) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var27 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var27 == nil {
|
||||||
|
templ_7745c5c3_Var27 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"block\"><div class=\"row row-cols-1 row-cols-md-4 g-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, course := range courses {
|
||||||
|
templ_7745c5c3_Err = listCoursesCard(course).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCoursesLearningLegacy(containers []CategoryContainer) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var28 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var28 == nil {
|
||||||
|
templ_7745c5c3_Var28 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
for _, container := range containers {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section class=\"row first-class-group g-4\"><h1 class=\"title\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var29 string
|
||||||
|
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(container.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 175, Col: 37}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, subcategory := range container.Subcategories {
|
||||||
|
templ_7745c5c3_Err = listCoursesThematicRowLegacy(container.ID, subcategory).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCoursesThematicRowLegacy(categoryID string, subcategory SubcategoryContainer) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var30 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var30 == nil {
|
||||||
|
templ_7745c5c3_Var30 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"block second-class-group\"><h3 class=\"title\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var31 templ.SafeURL = templ.SafeURL("/courses/" + categoryID + "/" + subcategory.ID)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var31)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"text-decoration-none\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var32 string
|
||||||
|
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(subcategory.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 187, Col: 22}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-arrow-up-right-square\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm5.854 8.803a.5.5 0 1 1-.708-.707L9.243 6H6.475a.5.5 0 1 1 0-1h3.975a.5.5 0 0 1 .5.5v3.975a.5.5 0 1 1-1 0V6.707z\"></path></svg></a></h3><p class=\"visually-hidden\">В категогрии ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var33 string
|
||||||
|
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(subcategory.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 193, Col: 71}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" собраны ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var34 string
|
||||||
|
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(subcategory.Count))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 193, Col: 122}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" курсов. Раз в неделю мы обновляем информацию о всех курсах.</p><div class=\"row row-cols-1 row-cols-md-4 g-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, info := range subcategory.Courses {
|
||||||
|
templ_7745c5c3_Err = listCoursesCard(info).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func myImg() templ.CSSClass {
|
||||||
|
var templ_7745c5c3_CSSBuilder strings.Builder
|
||||||
|
templ_7745c5c3_CSSBuilder.WriteString(`min-height:19rem;`)
|
||||||
|
templ_7745c5c3_CSSBuilder.WriteString(`min-width:19rem;`)
|
||||||
|
templ_7745c5c3_CSSID := templ.CSSID(`myImg`, templ_7745c5c3_CSSBuilder.String())
|
||||||
|
return templ.ComponentCSSClass{
|
||||||
|
ID: templ_7745c5c3_CSSID,
|
||||||
|
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cardTextSize() templ.CSSClass {
|
||||||
|
var templ_7745c5c3_CSSBuilder strings.Builder
|
||||||
|
templ_7745c5c3_CSSBuilder.WriteString(`min-height:12rem;`)
|
||||||
|
templ_7745c5c3_CSSID := templ.CSSID(`cardTextSize`, templ_7745c5c3_CSSBuilder.String())
|
||||||
|
return templ.ComponentCSSClass{
|
||||||
|
ID: templ_7745c5c3_CSSID,
|
||||||
|
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCoursesCard(info CourseInfo) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var35 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var35 == nil {
|
||||||
|
templ_7745c5c3_Var35 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col\" id=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var36 string
|
||||||
|
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(info.ID)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 213, Col: 30}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"card h-100\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var37 = []any{"card-img-top"}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var37...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img src=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var38 string
|
||||||
|
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(GetOrFallback(info.ImageLink, "https://placehold.co/128x128"))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 215, Col: 75}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" alt=\"Course picture\" class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var39 string
|
||||||
|
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var37).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var40 = []any{"card-body", cardTextSize(), "row"}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var40...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var41 string
|
||||||
|
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var40).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><h5 class=\"card-title\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var42 string
|
||||||
|
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(info.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 217, Col: 38}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h5><div class=\"input-group d-flex align-self-end\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var43 templ.SafeURL = templ.URL(info.OriginLink)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var43)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"btn text btn-outline-primary flex-grow-1\">Go!</a> <span class=\"input-group-text justify-content-end flex-fill\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var44 string
|
||||||
|
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(info.FullPrice))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/list.templ`, Line: 224, Col: 36}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" rub.</span></div></div></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListCourses(pageType PageKind, s stats, params ListCoursesParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var45 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var45 == nil {
|
||||||
|
templ_7745c5c3_Var45 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var46 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesSectionHeader(params.FilterForm.BreadcrumbsParams).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesSectionFilters(params.FilterForm).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesLearning(params.Courses).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = pagination(params.Pagination).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = root(pageType, s).Render(templ.WithChildren(ctx, templ_7745c5c3_Var46), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
96
internal/kurious/ports/http/bootstrap/main.templ
Normal file
96
internal/kurious/ports/http/bootstrap/main.templ
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
type IndexCourseCategoryItem struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
ExampleThemes []string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// courseItemCard is a card that renders a single course thematic item
|
||||||
|
// that holds multiple learning types. It expected to have a basic description
|
||||||
|
// and an amount of items.
|
||||||
|
templ courseItemCard(item IndexCourseCategoryItem) {
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{ item.Name }</h5>
|
||||||
|
<hr/>
|
||||||
|
<p>{ item.Description }</p>
|
||||||
|
if len(item.ExampleThemes) > 0 {
|
||||||
|
<p>В данной категории вы можете найти курсы по темам:</p>
|
||||||
|
<ul>
|
||||||
|
for _, exampleItem := range item.ExampleThemes {
|
||||||
|
<li>
|
||||||
|
<span class="d-inline-block text-truncate col-8">{ exampleItem }</span>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<a href={ templ.URL("/courses/" + item.ID) } class="btn btn-sm btn-outline-primary col-6">
|
||||||
|
Open
|
||||||
|
</a>
|
||||||
|
<small class="text-body-secondary">
|
||||||
|
{ strconv.Itoa(item.Count) } items.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ courseCategory(items []IndexCourseCategoryItem) {
|
||||||
|
<div class="container w-75 mb-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
for _, item := range items {
|
||||||
|
<div class="col-12 col-md-8 col-lg-4">
|
||||||
|
@courseItemCard(item)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pagination struct {
|
||||||
|
Page int
|
||||||
|
TotalPages int
|
||||||
|
BaseURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
templ pagination(p Pagination) {
|
||||||
|
if p.Page > 0 && p.TotalPages > 0 {
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<li class={ "page-item" , templ.KV("disabled", p.Page==1), }>
|
||||||
|
<a href={ templ.URL(p.BaseURL + "?page=" + strconv.Itoa(p.Page-1)) } class="page-link">Previous</a>
|
||||||
|
</li>
|
||||||
|
for i := max(p.Page-2, 1); i < min(p.TotalPages, 10); i++ {
|
||||||
|
<li
|
||||||
|
class={ "page-item" , templ.KV("active", p.Page==i), }
|
||||||
|
>
|
||||||
|
<a href={ templ.URL(p.BaseURL + "?page=" + strconv.Itoa(i)) } class="page-link">{ strconv.Itoa(i) }</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class={ "page-item" , templ.KV("disabled", p.Page==p.TotalPages), }>
|
||||||
|
<a href={ templ.URL(p.BaseURL + "?page=" + strconv.Itoa(p.Page+1)) } class="page-link">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MainPageParams struct {
|
||||||
|
Breadcrumbs BreadcrumbsParams
|
||||||
|
Categories []IndexCourseCategoryItem
|
||||||
|
Pagination Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MainPage(pageType PageKind, s stats, params MainPageParams) {
|
||||||
|
@root(pageType, s) {
|
||||||
|
@listCoursesSectionHeader(params.Breadcrumbs)
|
||||||
|
@courseCategory(params.Categories)
|
||||||
|
@pagination(params.Pagination)
|
||||||
|
}
|
||||||
|
}
|
||||||
372
internal/kurious/ports/http/bootstrap/main_templ.go
Normal file
372
internal/kurious/ports/http/bootstrap/main_templ.go
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.2.707
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import "context"
|
||||||
|
import "io"
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
type IndexCourseCategoryItem struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
ExampleThemes []string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// courseItemCard is a card that renders a single course thematic item
|
||||||
|
// that holds multiple learning types. It expected to have a basic description
|
||||||
|
// and an amount of items.
|
||||||
|
func courseItemCard(item IndexCourseCategoryItem) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"card\"><div class=\"card-body\"><h5 class=\"card-title\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 19, Col: 37}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h5><hr><p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(item.Description)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 21, Col: 24}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if len(item.ExampleThemes) > 0 {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>В данной категории вы можете найти курсы по темам:</p><ul>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, exampleItem := range item.ExampleThemes {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><span class=\"d-inline-block text-truncate col-8\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(exampleItem)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 27, Col: 69}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"d-flex justify-content-between align-items-center\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 templ.SafeURL = templ.URL("/courses/" + item.ID)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"btn btn-sm btn-outline-primary col-6\">Open</a> <small class=\"text-body-secondary\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(item.Count))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 37, Col: 31}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" items.</small></div></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func courseCategory(items []IndexCourseCategoryItem) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var7 == nil {
|
||||||
|
templ_7745c5c3_Var7 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"container w-75 mb-4\"><div class=\"row g-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"col-12 col-md-8 col-lg-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = courseItemCard(item).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pagination struct {
|
||||||
|
Page int
|
||||||
|
TotalPages int
|
||||||
|
BaseURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func pagination(p Pagination) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var8 == nil {
|
||||||
|
templ_7745c5c3_Var8 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
if p.Page > 0 && p.TotalPages > 0 {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav aria-label=\"Page navigation\"><ul class=\"pagination justify-content-center\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 = []any{"page-item", templ.KV("disabled", p.Page == 1)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var10 string
|
||||||
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var11 templ.SafeURL = templ.URL(p.BaseURL + "?page=" + strconv.Itoa(p.Page-1))
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var11)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"page-link\">Previous</a></li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for i := max(p.Page-2, 1); i < min(p.TotalPages, 10); i++ {
|
||||||
|
var templ_7745c5c3_Var12 = []any{"page-item", templ.KV("active", p.Page == i)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var14 templ.SafeURL = templ.URL(p.BaseURL + "?page=" + strconv.Itoa(i))
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var14)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"page-link\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var15 string
|
||||||
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(i))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 73, Col: 103}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var16 = []any{"page-item", templ.KV("disabled", p.Page == p.TotalPages)}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var17 string
|
||||||
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var16).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/bootstrap/main.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var18 templ.SafeURL = templ.URL(p.BaseURL + "?page=" + strconv.Itoa(p.Page+1))
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var18)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"page-link\">Next</a></li></ul></nav>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type MainPageParams struct {
|
||||||
|
Breadcrumbs BreadcrumbsParams
|
||||||
|
Categories []IndexCourseCategoryItem
|
||||||
|
Pagination Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
func MainPage(pageType PageKind, s stats, params MainPageParams) templ.Component {
|
||||||
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var19 == nil {
|
||||||
|
templ_7745c5c3_Var19 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var20 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = listCoursesSectionHeader(params.Breadcrumbs).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = courseCategory(params.Categories).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = pagination(params.Pagination).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = root(pageType, s).Render(templ.WithChildren(ctx, templ_7745c5c3_Var20), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||||
|
}
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,10 +1,18 @@
|
|||||||
package templ
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PageKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PageIndex PageKind = "main"
|
||||||
|
PageCourses PageKind = "courses"
|
||||||
|
PageAbout PageKind = "about"
|
||||||
|
)
|
||||||
|
|
||||||
func getCompactedValue(value int) string {
|
func getCompactedValue(value int) string {
|
||||||
var (
|
var (
|
||||||
myValue float64
|
myValue float64
|
||||||
@ -37,6 +45,29 @@ type stats struct {
|
|||||||
CategoriesCount string
|
CategoriesCount string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NameIDPair struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortingItem struct {
|
||||||
|
NameIDPair
|
||||||
|
|
||||||
|
Ascending bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortingView struct {
|
||||||
|
Items []SortingItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoursesFilterViewParams struct {
|
||||||
|
SelectedSchoolID string
|
||||||
|
Schools []NameIDPair
|
||||||
|
OrderBy string
|
||||||
|
OrderFields []NameIDPair
|
||||||
|
Ascending bool
|
||||||
|
}
|
||||||
|
|
||||||
type Category struct {
|
type Category struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
@ -54,12 +85,10 @@ type BreadcrumbsParams struct {
|
|||||||
type FilterFormParams struct {
|
type FilterFormParams struct {
|
||||||
BreadcrumbsParams
|
BreadcrumbsParams
|
||||||
|
|
||||||
|
Schools CoursesFilterViewParams
|
||||||
AvailableLearningTypes []Category
|
AvailableLearningTypes []Category
|
||||||
AvailableCourseThematics []Category
|
AvailableCourseThematics []Category
|
||||||
}
|
Render bool
|
||||||
|
|
||||||
func isEmpty(s string) bool {
|
|
||||||
return s == ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseInfo struct {
|
type CourseInfo struct {
|
||||||
@ -74,6 +103,7 @@ type CategoryBaseInfo struct {
|
|||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
|
Count int
|
||||||
}
|
}
|
||||||
|
|
||||||
type CategoryContainer struct {
|
type CategoryContainer struct {
|
||||||
@ -90,6 +120,38 @@ type SubcategoryContainer struct {
|
|||||||
|
|
||||||
type ListCoursesParams struct {
|
type ListCoursesParams struct {
|
||||||
FilterForm FilterFormParams
|
FilterForm FilterFormParams
|
||||||
|
|
||||||
Categories []CategoryContainer
|
Categories []CategoryContainer
|
||||||
|
Pagination Pagination
|
||||||
|
Items int
|
||||||
|
Courses []CourseInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOrFallback[T comparable](value T, fallback T) T {
|
||||||
|
var zeroValue T
|
||||||
|
if value == zeroValue {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
type ordered interface {
|
||||||
|
~int8 | ~int16 | ~int32 | ~int64 | ~int |
|
||||||
|
~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint |
|
||||||
|
~float32 | float64 | ~string
|
||||||
|
}
|
||||||
|
|
||||||
|
func min[T ordered](lhs, rhs T) T {
|
||||||
|
if lhs < rhs {
|
||||||
|
return lhs
|
||||||
|
}
|
||||||
|
|
||||||
|
return rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
func max[T ordered](lhs, rhs T) T {
|
||||||
|
if lhs > rhs {
|
||||||
|
return lhs
|
||||||
|
}
|
||||||
|
|
||||||
|
return rhs
|
||||||
}
|
}
|
||||||
@ -2,343 +2,405 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"slices"
|
||||||
"strconv"
|
"sync"
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/xslices"
|
"git.loyso.art/frx/kurious/internal/common/xslices"
|
||||||
"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/ports/http/bootstrap"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/service"
|
"git.loyso.art/frx/kurious/internal/kurious/service"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type courseServer struct {
|
var (
|
||||||
|
paramsAttr = attribute.Key("params")
|
||||||
|
|
||||||
|
webtracer = otel.Tracer("http")
|
||||||
|
)
|
||||||
|
|
||||||
|
type courseTemplServer struct {
|
||||||
app service.Application
|
app service.Application
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type pagination struct {
|
func makeTemplListCoursesParams(counts map[string]domain.LearningTypeStat, in ...domain.Course) bootstrap.ListCoursesParams {
|
||||||
nextPageToken string
|
coursesBySubcategory := make(map[string][]bootstrap.CourseInfo, len(in))
|
||||||
perPage int
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
|
|
||||||
query := r.URL.Query()
|
|
||||||
out.nextPageToken = query.Get("next")
|
|
||||||
|
|
||||||
if query.Has("per_page") {
|
|
||||||
out.perPage, err = strconv.Atoi(query.Get("per_page"))
|
|
||||||
if err != nil {
|
|
||||||
return out, errors.NewValidationError("per_page", "bad per_page value")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.perPage = 50
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
LearningTypePathParam = "learning_type"
|
|
||||||
ThematicTypePathParam = "thematic_type"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error) {
|
|
||||||
out.pagination, err = parsePaginationFromQuery(r)
|
|
||||||
if err != nil {
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
out.learningType = vars[LearningTypePathParam]
|
|
||||||
out.courseThematic = vars[ThematicTypePathParam]
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type listCoursesParams struct {
|
|
||||||
pagination
|
|
||||||
|
|
||||||
courseThematic string
|
|
||||||
learningType string
|
|
||||||
}
|
|
||||||
|
|
||||||
type baseInfo struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
type categoryInfo struct {
|
|
||||||
baseInfo
|
|
||||||
|
|
||||||
Subcategories []subcategoryInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type subcategoryInfo struct {
|
|
||||||
baseInfo
|
|
||||||
|
|
||||||
Courses []domain.Course
|
|
||||||
}
|
|
||||||
|
|
||||||
type IDNamePair struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
IsActive bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type listCoursesTemplateParams struct {
|
|
||||||
Categories []categoryInfo
|
|
||||||
NextPageToken string
|
|
||||||
AvailableLearningTypes []IDNamePair
|
|
||||||
AvailableCourseThematics []IDNamePair
|
|
||||||
|
|
||||||
ActiveLearningType string
|
|
||||||
LearningTypeName string
|
|
||||||
|
|
||||||
ActiveCourseThematic string
|
|
||||||
CourseThematicName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
|
|
||||||
coursesBySubcategory := make(map[string][]domain.Course, len(in))
|
|
||||||
subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
|
subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
|
||||||
categoryByID := make(map[string]baseInfo, len(in))
|
categoryByID := make(map[string]bootstrap.CategoryBaseInfo, len(in))
|
||||||
|
seenCourses := make(map[string]struct{}, len(in))
|
||||||
|
|
||||||
|
var out bootstrap.ListCoursesParams
|
||||||
xslices.ForEach(in, func(c domain.Course) {
|
xslices.ForEach(in, func(c domain.Course) {
|
||||||
coursesBySubcategory[c.ThematicID] = append(coursesBySubcategory[c.ThematicID], c)
|
courseInfo := bootstrap.CourseInfo{
|
||||||
|
ID: c.ID,
|
||||||
|
Name: c.Name,
|
||||||
|
FullPrice: int(c.FullPrice),
|
||||||
|
ImageLink: c.ImageLink,
|
||||||
|
OriginLink: c.OriginLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
coursesBySubcategory[c.ThematicID] = append(coursesBySubcategory[c.ThematicID], courseInfo)
|
||||||
|
|
||||||
if _, ok := subcategoriesByCategories[c.LearningTypeID]; !ok {
|
if _, ok := subcategoriesByCategories[c.LearningTypeID]; !ok {
|
||||||
subcategoriesByCategories[c.LearningTypeID] = map[string]struct{}{}
|
subcategoriesByCategories[c.LearningTypeID] = map[string]struct{}{}
|
||||||
}
|
}
|
||||||
subcategoriesByCategories[c.LearningTypeID][c.ThematicID] = struct{}{}
|
subcategoriesByCategories[c.LearningTypeID][c.ThematicID] = struct{}{}
|
||||||
|
|
||||||
if _, ok := categoryByID[c.LearningTypeID]; !ok {
|
if _, ok := categoryByID[c.LearningTypeID]; !ok {
|
||||||
categoryByID[c.LearningTypeID] = baseInfo{
|
categoryByID[c.LearningTypeID] = bootstrap.CategoryBaseInfo{
|
||||||
ID: c.LearningTypeID,
|
ID: c.LearningTypeID,
|
||||||
Name: c.LearningType,
|
Name: c.LearningType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := categoryByID[c.ThematicID]; !ok {
|
if _, ok := categoryByID[c.ThematicID]; !ok {
|
||||||
categoryByID[c.ThematicID] = baseInfo{
|
categoryByID[c.ThematicID] = bootstrap.CategoryBaseInfo{
|
||||||
ID: c.ThematicID,
|
ID: c.ThematicID,
|
||||||
Name: c.Thematic,
|
Name: c.Thematic,
|
||||||
|
Count: counts[c.LearningTypeID].CourseThematic[c.ThematicID],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := seenCourses[c.ExternalID.Value()]; ok && c.ExternalID.Valid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Courses = append(out.Courses, courseInfo)
|
||||||
|
seenCourses[c.ExternalID.Value()] = struct{}{}
|
||||||
})
|
})
|
||||||
|
|
||||||
var out listCoursesTemplateParams
|
for categoryID, subcategoriesID := range subcategoriesByCategories {
|
||||||
for category, subcategoryMap := range subcategoriesByCategories {
|
outCategory := bootstrap.CategoryContainer{
|
||||||
outCategory := categoryInfo{
|
CategoryBaseInfo: categoryByID[categoryID],
|
||||||
baseInfo: categoryByID[category],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for subcategory := range subcategoryMap {
|
for subcategoryID := range subcategoriesID {
|
||||||
outSubCategory := subcategoryInfo{
|
outSubcategory := bootstrap.SubcategoryContainer{
|
||||||
baseInfo: categoryByID[subcategory],
|
CategoryBaseInfo: categoryByID[subcategoryID],
|
||||||
Courses: coursesBySubcategory[subcategory],
|
Courses: coursesBySubcategory[subcategoryID],
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
|
func (c courseTemplServer) List(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
params, err := parseListCoursesParams(r)
|
var span trace.Span
|
||||||
|
ctx, span = webtracer.Start(ctx, "http_server.list")
|
||||||
|
defer func() {
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stats := bootstrap.MakeNewStats(10_240, 2_560_000, 1800)
|
||||||
|
|
||||||
|
pathParams, err := parseListCoursesParams(r)
|
||||||
if handleError(ctx, err, w, c.log, "unable to parse list courses params") {
|
if handleError(ctx, err, w, c.log, "unable to parse list courses params") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
|
jsonParams, _ := json.Marshal(pathParams)
|
||||||
CourseThematic: params.courseThematic,
|
span.SetAttributes(paramsAttr.String(string(jsonParams)))
|
||||||
LearningType: params.learningType,
|
|
||||||
Limit: params.perPage,
|
var offset int
|
||||||
NextPageToken: params.nextPageToken,
|
if pathParams.Page > 0 {
|
||||||
|
offset = (pathParams.Page - 1) * pathParams.PerPage
|
||||||
|
}
|
||||||
|
listCoursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
|
||||||
|
CourseThematic: pathParams.CourseThematic,
|
||||||
|
LearningType: pathParams.LearningType,
|
||||||
|
OrganizationID: pathParams.School,
|
||||||
|
OrderBy: orderByListing.getField(pathParams.OrderBy),
|
||||||
|
Ascending: pathParams.Ascending,
|
||||||
|
Limit: pathParams.PerPage,
|
||||||
|
NextPageToken: pathParams.NextPageToken,
|
||||||
|
Offset: offset,
|
||||||
})
|
})
|
||||||
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
courses := result.Courses
|
statsresult, err := c.app.Queries.ListCourseStatistics.Handle(ctx, query.ListCoursesStats{
|
||||||
templateCourses := mapDomainCourseToTemplate(courses...)
|
LearningTypeID: pathParams.LearningType,
|
||||||
templateCourses.NextPageToken = result.NextPageToken
|
CourseThematicsID: pathParams.CourseThematic,
|
||||||
|
OrganizationID: pathParams.School,
|
||||||
|
})
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to load stats") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
learningTypeList, err := c.app.Queries.ListLearningTypes.Handle(ctx, query.ListLearningTypes{})
|
params := makeTemplListCoursesParams(statsresult.StatsByLearningType, listCoursesResult.Courses...)
|
||||||
|
|
||||||
|
learningTypeResult, err := c.app.Queries.ListLearningTypes.Handle(ctx, query.ListLearningTypes{})
|
||||||
if handleError(ctx, err, w, c.log, "unable to list learning types") {
|
if handleError(ctx, err, w, c.log, "unable to list learning types") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templateCourses.AvailableLearningTypes = xslices.Map(learningTypeList.LearningTypes, func(in query.LearningType) IDNamePair {
|
params.FilterForm.AvailableLearningTypes = xslices.Map(learningTypeResult.LearningTypes, func(in query.LearningType) bootstrap.Category {
|
||||||
if in.ID == params.learningType {
|
outcategory := bootstrap.Category{
|
||||||
templateCourses.LearningTypeName = in.Name
|
|
||||||
}
|
|
||||||
return IDNamePair{
|
|
||||||
ID: in.ID,
|
ID: in.ID,
|
||||||
Name: in.Name,
|
Name: in.Name,
|
||||||
IsActive: in.ID == params.learningType,
|
|
||||||
}
|
}
|
||||||
|
if in.ID == pathParams.LearningType {
|
||||||
|
params.FilterForm.ActiveLearningType = outcategory
|
||||||
|
}
|
||||||
|
|
||||||
|
return outcategory
|
||||||
})
|
})
|
||||||
|
|
||||||
templateCourses.ActiveLearningType = params.learningType
|
if pathParams.LearningType != "" {
|
||||||
templateCourses.ActiveCourseThematic = params.courseThematic
|
|
||||||
|
|
||||||
if params.learningType != "" {
|
|
||||||
courseThematicsResult, err := c.app.Queries.ListCourseThematics.Handle(ctx, query.ListCourseThematics{
|
courseThematicsResult, err := c.app.Queries.ListCourseThematics.Handle(ctx, query.ListCourseThematics{
|
||||||
LearningTypeID: params.learningType,
|
LearningTypeID: pathParams.LearningType,
|
||||||
})
|
})
|
||||||
if handleError(ctx, err, w, c.log, "unable to list course thematics") {
|
if handleError(ctx, err, w, c.log, "unable to list course thematics") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templateCourses.AvailableCourseThematics = xslices.Map(courseThematicsResult.CourseThematics, func(in query.CourseThematic) IDNamePair {
|
params.FilterForm.AvailableCourseThematics = xslices.Map(courseThematicsResult.CourseThematics, func(in query.CourseThematic) bootstrap.Category {
|
||||||
if in.ID == params.courseThematic {
|
outcategory := bootstrap.Category{
|
||||||
templateCourses.CourseThematicName = in.Name
|
|
||||||
}
|
|
||||||
return IDNamePair{
|
|
||||||
ID: in.ID,
|
ID: in.ID,
|
||||||
Name: in.Name,
|
Name: in.Name,
|
||||||
IsActive: in.ID == params.courseThematic,
|
|
||||||
}
|
}
|
||||||
|
if pathParams.CourseThematic == in.ID {
|
||||||
|
params.FilterForm.ActiveCourseThematic = outcategory
|
||||||
|
}
|
||||||
|
|
||||||
|
return outcategory
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "courses", templateCourses)
|
organizaions, err := c.app.Queries.ListOrganizationsStats.Handle(ctx, query.ListOrganizationsStats{
|
||||||
if handleError(ctx, err, w, c.log, "unable to execute template") {
|
LearningTypeID: pathParams.LearningType,
|
||||||
|
CourseThematicID: pathParams.CourseThematic,
|
||||||
|
})
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to list organizations") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
organizationStatSortFunc := func(lhs, rhs domain.OrganizationStat) int {
|
||||||
|
if lhs.CoursesCount > rhs.CoursesCount {
|
||||||
|
return -1
|
||||||
|
} else if lhs.CoursesCount < rhs.CoursesCount {
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c courseServer) Get(w http.ResponseWriter, r *http.Request) {
|
if lhs.ID > rhs.ID {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(organizaions, organizationStatSortFunc)
|
||||||
|
|
||||||
|
schools := xslices.Map(organizaions, func(in domain.OrganizationStat) bootstrap.NameIDPair {
|
||||||
|
return bootstrap.NameIDPair{
|
||||||
|
ID: in.ID,
|
||||||
|
Name: fmt.Sprintf("%s (count: %d)", in.Name, in.CoursesCount),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
params = bootstrap.ListCoursesParams{
|
||||||
|
FilterForm: bootstrap.FilterFormParams{
|
||||||
|
Render: true,
|
||||||
|
BreadcrumbsParams: bootstrap.BreadcrumbsParams{
|
||||||
|
ActiveLearningType: params.FilterForm.ActiveLearningType,
|
||||||
|
ActiveCourseThematic: params.FilterForm.ActiveCourseThematic,
|
||||||
|
},
|
||||||
|
AvailableLearningTypes: params.FilterForm.AvailableLearningTypes,
|
||||||
|
AvailableCourseThematics: params.FilterForm.AvailableCourseThematics,
|
||||||
|
Schools: bootstrap.CoursesFilterViewParams{
|
||||||
|
SelectedSchoolID: pathParams.School,
|
||||||
|
Schools: schools,
|
||||||
|
Ascending: pathParams.Ascending,
|
||||||
|
OrderBy: pathParams.OrderBy,
|
||||||
|
OrderFields: orderByListing.asNameIDPair(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Courses: params.Courses,
|
||||||
|
Categories: params.Categories,
|
||||||
|
Pagination: bootstrap.Pagination{
|
||||||
|
Page: pathParams.Page,
|
||||||
|
TotalPages: listCoursesResult.Count / pathParams.PerPage,
|
||||||
|
BaseURL: r.URL.Path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.log.DebugContext(
|
||||||
|
ctx, "params rendered",
|
||||||
|
slog.Int("course_thematic", len(params.FilterForm.AvailableCourseThematics)),
|
||||||
|
slog.Int("learning_type", len(params.FilterForm.AvailableLearningTypes)),
|
||||||
|
slog.Int("items", len(listCoursesResult.Courses)),
|
||||||
|
slog.Int("page", params.Pagination.Page),
|
||||||
|
slog.Int("total_pages", params.Pagination.TotalPages),
|
||||||
|
)
|
||||||
|
|
||||||
|
slices.SortFunc(params.Categories, func(lhs, rhs bootstrap.CategoryContainer) int {
|
||||||
|
if lhs.Count > rhs.Count {
|
||||||
|
return 1
|
||||||
|
} else if lhs.Count < rhs.Count {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
span.AddEvent("starting to render")
|
||||||
|
if pathParams.CourseThematic == "" {
|
||||||
|
params.FilterForm.Render = true
|
||||||
|
err = bootstrap.ListCourseThematics(bootstrap.PageCourses, stats, params).Render(ctx, w)
|
||||||
|
} else {
|
||||||
|
err = bootstrap.ListCourses(bootstrap.PageCourses, stats, params).Render(ctx, w)
|
||||||
|
}
|
||||||
|
span.AddEvent("render finished")
|
||||||
|
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to render list courses") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "request completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c courseTemplServer) Index(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
id := mux.Vars(r)["course_id"]
|
var span trace.Span
|
||||||
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
|
ctx, span = webtracer.Start(ctx, "http_server.index")
|
||||||
|
defer func() {
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stats := bootstrap.MakeNewStats(1, 2, 3)
|
||||||
|
|
||||||
|
coursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{})
|
||||||
|
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := bootstrap.MainPageParams{
|
||||||
|
Categories: []bootstrap.IndexCourseCategoryItem{},
|
||||||
|
}
|
||||||
|
|
||||||
|
coursesByLearningType := make(map[IDNamePair][]domain.Course)
|
||||||
|
xslices.ForEach(coursesResult.Courses, func(in domain.Course) {
|
||||||
|
pair := IDNamePair{
|
||||||
|
ID: in.LearningTypeID,
|
||||||
|
Name: in.LearningType,
|
||||||
|
}
|
||||||
|
coursesByLearningType[pair] = append(coursesByLearningType[pair], in)
|
||||||
|
})
|
||||||
|
|
||||||
|
for learningTypeInfo, courses := range coursesByLearningType {
|
||||||
|
category := bootstrap.IndexCourseCategoryItem{
|
||||||
|
ID: learningTypeInfo.ID,
|
||||||
|
Name: learningTypeInfo.Name,
|
||||||
|
Count: len(courses),
|
||||||
|
}
|
||||||
|
|
||||||
|
xslices.Shuffle(courses)
|
||||||
|
if len(courses) > 3 {
|
||||||
|
courses = courses[:3]
|
||||||
|
}
|
||||||
|
|
||||||
|
names := xslices.Map(courses, func(in domain.Course) string {
|
||||||
|
return in.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
category.ExampleThemes = names
|
||||||
|
|
||||||
|
params.Categories = append(params.Categories, category)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(params.Categories, func(lhs, rhs bootstrap.IndexCourseCategoryItem) int {
|
||||||
|
if lhs.Count < rhs.Count {
|
||||||
|
return 1
|
||||||
|
} else if lhs.Count > rhs.Count {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
span.AddEvent("starting to render")
|
||||||
|
err = bootstrap.MainPage(bootstrap.PageIndex, stats, params).Render(ctx, w)
|
||||||
|
span.AddEvent("render finished")
|
||||||
|
if handleError(ctx, err, w, c.log, "rendeting template") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
span.SetStatus(codes.Ok, "request completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderByListing = newOrderableContainer(
|
||||||
|
newOrderableUnit("pr", "Price", "full_price"),
|
||||||
|
newOrderableUnit("na", "Name", "name"),
|
||||||
|
newOrderableUnit("di", "Discount", "discount"),
|
||||||
|
newOrderableUnit("du", "Duration", "duration"),
|
||||||
|
newOrderableUnit("st", "Starts At", "starts_at"),
|
||||||
|
)
|
||||||
|
|
||||||
|
type orderableUnit struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOrderableUnit(id, name, field string) orderableUnit {
|
||||||
|
return orderableUnit{
|
||||||
ID: id,
|
ID: id,
|
||||||
})
|
Name: name,
|
||||||
if handleError(ctx, err, w, c.log, "unable to get course") {
|
Field: field,
|
||||||
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) GetShort(w http.ResponseWriter, r *http.Request) {
|
type orderableContainer struct {
|
||||||
ctx := r.Context()
|
nameByID map[string]string
|
||||||
|
fieldByID map[string]string
|
||||||
|
cachedNameIDPair []bootstrap.NameIDPair
|
||||||
|
makeCache sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
id := mux.Vars(r)["course_id"]
|
func (c *orderableContainer) asNameIDPair() []bootstrap.NameIDPair {
|
||||||
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
|
c.makeCache.Do(func() {
|
||||||
|
c.cachedNameIDPair = make([]bootstrap.NameIDPair, 0, len(c.nameByID))
|
||||||
|
for id, name := range c.nameByID {
|
||||||
|
c.cachedNameIDPair = append(c.cachedNameIDPair, bootstrap.NameIDPair{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
Name: name,
|
||||||
})
|
})
|
||||||
if handleError(ctx, err, w, c.log, "unable to get course") {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "course_info", course)
|
|
||||||
if handleError(ctx, err, w, c.log, "unable to execute template") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c courseServer) RenderEditDescription(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
|
return c.cachedNameIDPair
|
||||||
}
|
}
|
||||||
|
|
||||||
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "edit_description", course)
|
func (c *orderableContainer) getField(id string) string {
|
||||||
if handleError(ctx, err, w, c.log, "unable to execute template") {
|
return c.fieldByID[id]
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c courseServer) UpdateCourseDescription(w http.ResponseWriter, r *http.Request) {
|
func newOrderableContainer(units ...orderableUnit) *orderableContainer {
|
||||||
type requestModel struct {
|
nameByID := make(map[string]string, len(units))
|
||||||
ID string `json:"-"`
|
fieldByID := make(map[string]string, len(units))
|
||||||
Text string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := r.Context()
|
xslices.ForEach(units, func(u orderableUnit) {
|
||||||
|
nameByID[u.ID] = u.Name
|
||||||
var req requestModel
|
fieldByID[u.ID] = u.Field
|
||||||
req.ID = mux.Vars(r)["course_id"]
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
|
return &orderableContainer{
|
||||||
ID: req.ID,
|
nameByID: nameByID,
|
||||||
})
|
fieldByID: fieldByID,
|
||||||
if handleError(ctx, err, w, c.log, "unable to get course") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "course_info", course)
|
|
||||||
if handleError(ctx, err, w, c.log, "unable to execute template") {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/xslices"
|
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/app/query"
|
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
|
||||||
xtempl "git.loyso.art/frx/kurious/internal/kurious/ports/http/templ"
|
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type courseTemplServer struct {
|
|
||||||
app service.Application
|
|
||||||
log *slog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTemplListCoursesParams(in ...domain.Course) xtempl.ListCoursesParams {
|
|
||||||
coursesBySubcategory := make(map[string][]xtempl.CourseInfo, len(in))
|
|
||||||
subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
|
|
||||||
categoryByID := make(map[string]xtempl.CategoryBaseInfo, len(in))
|
|
||||||
|
|
||||||
xslices.ForEach(in, func(c domain.Course) {
|
|
||||||
courseInfo := xtempl.CourseInfo{
|
|
||||||
ID: c.ID,
|
|
||||||
Name: c.Name,
|
|
||||||
FullPrice: int(c.FullPrice),
|
|
||||||
ImageLink: c.ImageLink,
|
|
||||||
OriginLink: c.OriginLink,
|
|
||||||
}
|
|
||||||
|
|
||||||
coursesBySubcategory[c.ThematicID] = append(coursesBySubcategory[c.ThematicID], courseInfo)
|
|
||||||
|
|
||||||
if _, ok := subcategoriesByCategories[c.LearningTypeID]; !ok {
|
|
||||||
subcategoriesByCategories[c.LearningTypeID] = map[string]struct{}{}
|
|
||||||
}
|
|
||||||
subcategoriesByCategories[c.LearningTypeID][c.ThematicID] = struct{}{}
|
|
||||||
|
|
||||||
if _, ok := categoryByID[c.LearningTypeID]; !ok {
|
|
||||||
categoryByID[c.LearningTypeID] = xtempl.CategoryBaseInfo{
|
|
||||||
ID: c.LearningTypeID,
|
|
||||||
Name: c.LearningType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := categoryByID[c.ThematicID]; !ok {
|
|
||||||
categoryByID[c.ThematicID] = xtempl.CategoryBaseInfo{
|
|
||||||
ID: c.ThematicID,
|
|
||||||
Name: c.Thematic,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var out xtempl.ListCoursesParams
|
|
||||||
for categoryID, subcategoriesID := range subcategoriesByCategories {
|
|
||||||
outCategory := xtempl.CategoryContainer{
|
|
||||||
CategoryBaseInfo: categoryByID[categoryID],
|
|
||||||
}
|
|
||||||
|
|
||||||
for subcategoryID := range subcategoriesID {
|
|
||||||
outSubcategory := xtempl.SubcategoryContainer{
|
|
||||||
CategoryBaseInfo: categoryByID[subcategoryID],
|
|
||||||
Courses: coursesBySubcategory[subcategoryID],
|
|
||||||
}
|
|
||||||
|
|
||||||
outCategory.Subcategories = append(outCategory.Subcategories, outSubcategory)
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Categories = append(out.Categories, outCategory)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c courseTemplServer) List(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
stats := xtempl.MakeNewStats(10_240, 2_560_000, 1800)
|
|
||||||
|
|
||||||
pathParams, err := parseListCoursesParams(r)
|
|
||||||
if handleError(ctx, err, w, c.log, "unable to parse list courses params") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
listCoursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
|
|
||||||
CourseThematic: pathParams.courseThematic,
|
|
||||||
LearningType: pathParams.learningType,
|
|
||||||
Limit: pathParams.perPage,
|
|
||||||
NextPageToken: pathParams.nextPageToken,
|
|
||||||
})
|
|
||||||
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params := makeTemplListCoursesParams(listCoursesResult.Courses...)
|
|
||||||
|
|
||||||
learningTypeResult, err := c.app.Queries.ListLearningTypes.Handle(ctx, query.ListLearningTypes{})
|
|
||||||
if handleError(ctx, err, w, c.log, "unable to list learning types") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params.FilterForm.AvailableLearningTypes = xslices.Map(learningTypeResult.LearningTypes, func(in query.LearningType) xtempl.Category {
|
|
||||||
outcategory := xtempl.Category{
|
|
||||||
ID: in.ID,
|
|
||||||
Name: in.Name,
|
|
||||||
}
|
|
||||||
if in.ID == pathParams.learningType {
|
|
||||||
params.FilterForm.BreadcrumbsParams.ActiveLearningType = outcategory
|
|
||||||
}
|
|
||||||
|
|
||||||
return outcategory
|
|
||||||
})
|
|
||||||
|
|
||||||
if pathParams.learningType != "" {
|
|
||||||
courseThematicsResult, err := c.app.Queries.ListCourseThematics.Handle(ctx, query.ListCourseThematics{
|
|
||||||
LearningTypeID: pathParams.learningType,
|
|
||||||
})
|
|
||||||
if handleError(ctx, err, w, c.log, "unab;e to list course thematics") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
params.FilterForm.AvailableCourseThematics = xslices.Map(courseThematicsResult.CourseThematics, func(in query.CourseThematic) xtempl.Category {
|
|
||||||
outcategory := xtempl.Category{
|
|
||||||
ID: in.ID,
|
|
||||||
Name: in.Name,
|
|
||||||
}
|
|
||||||
if pathParams.courseThematic == in.ID {
|
|
||||||
params.FilterForm.BreadcrumbsParams.ActiveCourseThematic = outcategory
|
|
||||||
}
|
|
||||||
|
|
||||||
return outcategory
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err = xtempl.ListCourses(stats, params).Render(ctx, w)
|
|
||||||
if handleError(ctx, err, w, c.log, "unable to render list courses") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,192 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"html/template"
|
|
||||||
"io/fs"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/xslices"
|
|
||||||
)
|
|
||||||
|
|
||||||
const baseTemplatePath = "./internal/kurious/ports/http/templates/"
|
|
||||||
|
|
||||||
func must[T any](t T, err error) T {
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func scanFiles() []string {
|
|
||||||
entries := xslices.Map(
|
|
||||||
must(os.ReadDir(baseTemplatePath)),
|
|
||||||
func(v fs.DirEntry) string {
|
|
||||||
return path.Join(baseTemplatePath, v.Name())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCoreTemplate(ctx context.Context, log *slog.Logger) *template.Template {
|
|
||||||
filenames := scanFiles()
|
|
||||||
out, err := template.New("courses").ParseFiles(filenames...)
|
|
||||||
if err != nil {
|
|
||||||
xcontext.LogWithWarnError(ctx, log, err, "unable to parse template")
|
|
||||||
|
|
||||||
return listTemplateParsed
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
var listTemplateParsed = template.Must(
|
|
||||||
template.New("courses").
|
|
||||||
Parse(listTemplate),
|
|
||||||
)
|
|
||||||
|
|
||||||
const listTemplate = `{{define "courses"}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Courses</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
header h1 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
nav ul {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
nav li {
|
|
||||||
display: inline;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
nav a {
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
h1, h2, h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
.course-plate a {
|
|
||||||
color: #333;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.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>
|
|
||||||
<header>
|
|
||||||
<h1>Courses</h1>
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/">Main page</a></li>
|
|
||||||
<li><a href="/about">About us</a></li>
|
|
||||||
<li><a href="/help">Help</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
{{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}}
|
|
||||||
<button onclick="window.location.href='/courses/?next={{.NextPageToken}}'">Next Page</button>
|
|
||||||
|
|
||||||
<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}}`
|
|
||||||
@ -5,10 +5,16 @@ import (
|
|||||||
stderrors "errors"
|
stderrors "errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"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/xcontext"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/service"
|
"git.loyso.art/frx/kurious/internal/kurious/service"
|
||||||
|
"git.loyso.art/frx/kurious/pkg/xdefault"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@ -23,11 +29,7 @@ func NewServer(app service.Application, log *slog.Logger) Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) Courses() courseServer {
|
func (s Server) Courses() courseTemplServer {
|
||||||
return courseServer(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Server) CoursesByTempl() courseTemplServer {
|
|
||||||
return courseTemplServer(s)
|
return courseTemplServer(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +38,10 @@ func handleError(ctx context.Context, err error, w http.ResponseWriter, log *slo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span := trace.SpanFromContext(ctx)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, "error during handling request")
|
||||||
|
|
||||||
var errorString string
|
var errorString string
|
||||||
var code int
|
var code int
|
||||||
valErr := new(errors.ValidationError)
|
valErr := new(errors.ValidationError)
|
||||||
@ -57,3 +63,79 @@ func handleError(ctx context.Context, err error, w http.ResponseWriter, log *slo
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pagination struct {
|
||||||
|
NextPageToken string
|
||||||
|
PerPage int
|
||||||
|
Page int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
|
||||||
|
query := r.URL.Query()
|
||||||
|
|
||||||
|
if query.Has("next") && query.Has("page") {
|
||||||
|
return out, errors.NewValidationError("next", `could not be set together with "page"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.NextPageToken = query.Get("next")
|
||||||
|
|
||||||
|
if query.Has("per_page") {
|
||||||
|
out.PerPage, err = strconv.Atoi(query.Get("per_page"))
|
||||||
|
if err != nil {
|
||||||
|
return out, errors.NewValidationError("per_page", "bad per_page value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.PerPage = 20
|
||||||
|
}
|
||||||
|
if query.Has("page") {
|
||||||
|
out.Page, err = strconv.Atoi(query.Get("page"))
|
||||||
|
if err != nil {
|
||||||
|
return out, errors.NewValidationError("page", "bad per_page value")
|
||||||
|
}
|
||||||
|
} else if !query.Has("next") {
|
||||||
|
out.Page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
LearningTypePathParam = "learning_type"
|
||||||
|
ThematicTypePathParam = "thematic_type"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error) {
|
||||||
|
out.pagination, err = parsePaginationFromQuery(r)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
out.LearningType = vars[LearningTypePathParam]
|
||||||
|
out.CourseThematic = vars[ThematicTypePathParam]
|
||||||
|
|
||||||
|
out.School = r.URL.Query().Get("school_id")
|
||||||
|
out.OrderBy = xdefault.WithFallback(r.URL.Query().Get("order_by"), "price")
|
||||||
|
|
||||||
|
if r.URL.Query().Has("asc") {
|
||||||
|
out.Ascending, _ = strconv.ParseBool(r.URL.Query().Get("asc"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type listCoursesParams struct {
|
||||||
|
pagination
|
||||||
|
|
||||||
|
CourseThematic string
|
||||||
|
LearningType string
|
||||||
|
School string
|
||||||
|
OrderBy string
|
||||||
|
Ascending bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type IDNamePair struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
IsActive bool
|
||||||
|
}
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
package templ
|
|
||||||
|
|
||||||
templ head() {
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>Courses Aggregator</title>
|
|
||||||
<script src="https://unpkg.com/htmx.org@1.8.0"></script>
|
|
||||||
<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"/>
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/>
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/>
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/>
|
|
||||||
<link rel="manifest" href="/site.webmanifest"/>
|
|
||||||
</head>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ navigation() {
|
|
||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
Courses
|
|
||||||
</div>
|
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
<div id="navbarBasicExample" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item">
|
|
||||||
Home
|
|
||||||
</a>
|
|
||||||
<a class="navbar-item">
|
|
||||||
Find
|
|
||||||
</a>
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a class="navbar-link">
|
|
||||||
More
|
|
||||||
</a>
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<a class="navbar-item">
|
|
||||||
About
|
|
||||||
</a>
|
|
||||||
<a class="navbar-item">
|
|
||||||
Contact
|
|
||||||
</a>
|
|
||||||
<hr class="navbar-divider"/>
|
|
||||||
<a class="navbar-item">
|
|
||||||
Report an issue
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ footer() {
|
|
||||||
<footer>
|
|
||||||
Here will be a footer
|
|
||||||
</footer>
|
|
||||||
}
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.513
|
|
||||||
package templ
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import "context"
|
|
||||||
import "io"
|
|
||||||
import "bytes"
|
|
||||||
|
|
||||||
func head() templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var2 := `Courses Aggregator`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title><script src=\"https://unpkg.com/htmx.org@1.8.0\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var3 := ``
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</script><script src=\"https://unpkg.com/htmx.org/dist/ext/json-enc.js\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var4 := ``
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</script><link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css\"><link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\"><link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\"><link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\"><link rel=\"manifest\" href=\"/site.webmanifest\"></head>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func navigation() templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var5 == nil {
|
|
||||||
templ_7745c5c3_Var5 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"navbar\" role=\"navigation\" aria-label=\"main navigation\"><div class=\"navbar-brand\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var6 := `Courses`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><a role=\"button\" class=\"navbar-burger\" aria-label=\"menu\" aria-expanded=\"false\"><span aria-hidden=\"true\"></span> <span aria-hidden=\"true\"></span> <span aria-hidden=\"true\"></span></a><div id=\"navbarBasicExample\" class=\"navbar-menu\"><div class=\"navbar-start\"><a class=\"navbar-item\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var7 := `Home`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> <a class=\"navbar-item\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var8 := `Find`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a><div class=\"navbar-item has-dropdown is-hoverable\"><a class=\"navbar-link\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var9 := `More`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a><div class=\"navbar-dropdown\"><a class=\"navbar-item\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var10 := `About`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a> <a class=\"navbar-item\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var11 := `Contact`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a><hr class=\"navbar-divider\"><a class=\"navbar-item\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var12 := `Report an issue`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div></div></div></div></nav>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func footer() templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var13 == nil {
|
|
||||||
templ_7745c5c3_Var13 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var14 := `Here will be a footer`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</footer>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,209 +0,0 @@
|
|||||||
package templ
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
script breadcrumbsLoad() {
|
|
||||||
const formFilterOnSubmit = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const lt = document.getElementById('learning-type-filter');
|
|
||||||
const ct = document.getElementById('course-thematic-filter');
|
|
||||||
|
|
||||||
const prefix = (lt !== null && lt.value !== '') ? `/courses/${lt.value}` : `/courses`;
|
|
||||||
const out = (ct !== null && ct.value !== '') ? `${prefix}/${ct.value}` : prefix;
|
|
||||||
|
|
||||||
document.location.assign(out);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const ff = document.getElementById('filter-form');
|
|
||||||
if (ff === null) return;
|
|
||||||
ff.addEventListener('submit', formFilterOnSubmit);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
templ breadcrumbItem(enabled bool, link string, isLink bool, title string) {
|
|
||||||
if enabled {
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
if !isEmpty(link) {
|
|
||||||
href={ templ.SafeURL("/courses" + link) }
|
|
||||||
itemprop="url"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
itemprop="title"
|
|
||||||
if isEmpty(link) {
|
|
||||||
itemprop="url"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{ title }
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templ listCourseHeader(params FilterFormParams) {
|
|
||||||
<div class="container block">
|
|
||||||
@breadcrumb(params.BreadcrumbsParams)
|
|
||||||
@filterForm(params)
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ breadcrumb(params BreadcrumbsParams) {
|
|
||||||
<nav
|
|
||||||
class="breadcrumb"
|
|
||||||
aria-label="breadcrumbs"
|
|
||||||
itemprop="breadcrumb"
|
|
||||||
itemtype="https://schema.org/BreadcrumbList"
|
|
||||||
itemscope
|
|
||||||
>
|
|
||||||
<ul>
|
|
||||||
@breadcrumbItem(true, "/courses", !isEmpty(params.ActiveLearningType.ID), "Курсы")
|
|
||||||
@breadcrumbItem(!params.ActiveLearningType.Empty(), "/" + params.ActiveLearningType.ID, !params.ActiveCourseThematic.Empty(), params.ActiveLearningType.Name)
|
|
||||||
@breadcrumbItem(!params.ActiveCourseThematic.Empty(), "", false, params.ActiveCourseThematic.Name)
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ filterForm(params FilterFormParams) {
|
|
||||||
<form id="filter-form" class="columns">
|
|
||||||
<div class="select">
|
|
||||||
<select id="learning-type-filter" name="learning_type">
|
|
||||||
<option value="">All learnings</option>
|
|
||||||
for _, item := range params.AvailableLearningTypes {
|
|
||||||
<option
|
|
||||||
value={ item.ID }
|
|
||||||
if item.ID == params.ActiveLearningType.ID {
|
|
||||||
selected
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{ item.Name }
|
|
||||||
</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
if !params.ActiveLearningType.Empty() {
|
|
||||||
<div class="select">
|
|
||||||
<select id="course-thematic-filter" name="course_thematic">
|
|
||||||
<option value="">All course thematics</option>
|
|
||||||
for _, item := range params.AvailableCourseThematics {
|
|
||||||
<option
|
|
||||||
value={ item.ID }
|
|
||||||
if item.ID == params.ActiveCourseThematic.ID {
|
|
||||||
selected
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{ item.Name }
|
|
||||||
</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@button("goto", templ.Attributes{"id": "go-to-filter"})
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ listCoursesContainer(categories []CategoryContainer) {
|
|
||||||
<div id="category-course-list" class="container">
|
|
||||||
for _, category := range categories {
|
|
||||||
<div class="box">
|
|
||||||
<div class="title is-3">
|
|
||||||
<a href={ templ.URL("/courses/" + category.ID) }>{ category.Name }</a>
|
|
||||||
</div>
|
|
||||||
<div class="subtitle is-6">
|
|
||||||
This category contains a lot of interesing courses. Check them out!
|
|
||||||
</div>
|
|
||||||
for _, subcategory := range category.Subcategories {
|
|
||||||
<div class="box">
|
|
||||||
<div class="title is-4">
|
|
||||||
<a href={ templ.URL(fmt.Sprintf("/courses/%s/%s", category.ID, subcategory.ID)) }>
|
|
||||||
{ subcategory.Name }
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
for _, course := range subcategory.Courses {
|
|
||||||
@courseInfoElement(course)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ ListCourses(s stats, params ListCoursesParams) {
|
|
||||||
@root(s) {
|
|
||||||
@listCourseHeader(params.FilterForm)
|
|
||||||
@listCoursesContainer(params.Categories)
|
|
||||||
|
|
||||||
<div id="course-info"></div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templ root(s stats) {
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
@head()
|
|
||||||
<body>
|
|
||||||
@navigation()
|
|
||||||
<nav class="level">
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Courses</p>
|
|
||||||
<p class="title">{ s.CoursesCount }</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Clients</p>
|
|
||||||
<p class="title">{ s.ClientsCount }</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Categories</p>
|
|
||||||
<p class="title">{ s.CategoriesCount }</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="section">
|
|
||||||
{ children... }
|
|
||||||
</div>
|
|
||||||
@footer()
|
|
||||||
@breadcrumbsLoad()
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ courseInfoElement(params CourseInfo) {
|
|
||||||
<article class="column is-one-quarter" hx-target="this" hx-swap="outerHTML">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<img src={ params.ImageLink }/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-5">{ params.Name }</p>
|
|
||||||
<p class="subtitle is-8">oh well</p>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
if params.FullPrice > 0 {
|
|
||||||
<p>{ strconv.Itoa(params.FullPrice) } руб.</p>
|
|
||||||
} else {
|
|
||||||
<p>Бесплатно</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@buttonRedirect(params.ID, "Show course", params.OriginLink)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
}
|
|
||||||
@ -1,714 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.513
|
|
||||||
package templ
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import "context"
|
|
||||||
import "io"
|
|
||||||
import "bytes"
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func breadcrumbsLoad() templ.ComponentScript {
|
|
||||||
return templ.ComponentScript{
|
|
||||||
Name: `__templ_breadcrumbsLoad_9a1d`,
|
|
||||||
Function: `function __templ_breadcrumbsLoad_9a1d(){const formFilterOnSubmit = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const lt = document.getElementById('learning-type-filter');
|
|
||||||
const ct = document.getElementById('course-thematic-filter');
|
|
||||||
|
|
||||||
const prefix = (lt !== null && lt.value !== '') ? ` + "`" + `/courses/${lt.value}` + "`" + ` : ` + "`" + `/courses` + "`" + `;
|
|
||||||
const out = (ct !== null && ct.value !== '') ? ` + "`" + `${prefix}/${ct.value}` + "`" + ` : prefix;
|
|
||||||
|
|
||||||
document.location.assign(out);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const ff = document.getElementById('filter-form');
|
|
||||||
if (ff === null) return;
|
|
||||||
ff.addEventListener('submit', formFilterOnSubmit);
|
|
||||||
});}`,
|
|
||||||
Call: templ.SafeScript(`__templ_breadcrumbsLoad_9a1d`),
|
|
||||||
CallInline: templ.SafeScriptInline(`__templ_breadcrumbsLoad_9a1d`),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func breadcrumbItem(enabled bool, link string, isLink bool, title string) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
if enabled {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !isEmpty(link) {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 templ.SafeURL = templ.SafeURL("/courses" + link)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" itemprop=\"url\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("><span itemprop=\"title\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if isEmpty(link) {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" itemprop=\"url\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 41, Col: 12}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></a></li>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func listCourseHeader(params FilterFormParams) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var4 == nil {
|
|
||||||
templ_7745c5c3_Var4 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"container block\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = breadcrumb(params.BreadcrumbsParams).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = filterForm(params).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func breadcrumb(params BreadcrumbsParams) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var5 == nil {
|
|
||||||
templ_7745c5c3_Var5 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"breadcrumb\" aria-label=\"breadcrumbs\" itemprop=\"breadcrumb\" itemtype=\"https://schema.org/BreadcrumbList\" itemscope><ul>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = breadcrumbItem(true, "/courses", !isEmpty(params.ActiveLearningType.ID), "Курсы").Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = breadcrumbItem(!params.ActiveLearningType.Empty(), "/"+params.ActiveLearningType.ID, !params.ActiveCourseThematic.Empty(), params.ActiveLearningType.Name).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = breadcrumbItem(!params.ActiveCourseThematic.Empty(), "", false, params.ActiveCourseThematic.Name).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></nav>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterForm(params FilterFormParams) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var6 == nil {
|
|
||||||
templ_7745c5c3_Var6 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"filter-form\" class=\"columns\"><div class=\"select\"><select id=\"learning-type-filter\" name=\"learning_type\"><option value=\"\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var7 := `All learnings`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, item := range params.AvailableLearningTypes {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(item.ID))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if item.ID == params.ActiveLearningType.ID {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var8 string
|
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 83, Col: 23}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !params.ActiveLearningType.Empty() {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"select\"><select id=\"course-thematic-filter\" name=\"course_thematic\"><option value=\"\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var9 := `All course thematics`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, item := range params.AvailableCourseThematics {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(item.ID))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if item.ID == params.ActiveCourseThematic.ID {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" selected")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var10 string
|
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 99, Col: 25}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</option>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = button("goto", templ.Attributes{"id": "go-to-filter"}).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func listCoursesContainer(categories []CategoryContainer) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var11 == nil {
|
|
||||||
templ_7745c5c3_Var11 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"category-course-list\" class=\"container\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, category := range categories {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"box\"><div class=\"title is-3\"><a href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var12 templ.SafeURL = templ.URL("/courses/" + category.ID)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var13 string
|
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(category.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 114, Col: 69}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></div><div class=\"subtitle is-6\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var14 := `This category contains a lot of interesing courses. Check them out!`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, subcategory := range category.Subcategories {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"box\"><div class=\"title is-4\"><a href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var15 templ.SafeURL = templ.URL(fmt.Sprintf("/courses/%s/%s", category.ID, subcategory.ID))
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var15)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var16 string
|
|
||||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(subcategory.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 123, Col: 26}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a><div class=\"columns is-multiline\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, course := range subcategory.Courses {
|
|
||||||
templ_7745c5c3_Err = courseInfoElement(course).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListCourses(s stats, params ListCoursesParams) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var17 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var17 == nil {
|
|
||||||
templ_7745c5c3_Var17 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Var18 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = listCourseHeader(params.FilterForm).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = listCoursesContainer(params.Categories).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div id=\"course-info\"></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
templ_7745c5c3_Err = root(s).Render(templ.WithChildren(ctx, templ_7745c5c3_Var18), templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func root(s stats) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var19 == nil {
|
|
||||||
templ_7745c5c3_Var19 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = head().Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = navigation().Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"level\"><div class=\"level-item has-text-centered\"><div><p class=\"heading\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var20 := `Courses`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"title\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var21 string
|
|
||||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(s.CoursesCount)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 158, Col: 39}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div><div class=\"level-item has-text-centered\"><div><p class=\"heading\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var22 := `Clients`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"title\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var23 string
|
|
||||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(s.ClientsCount)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 164, Col: 39}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div><div class=\"level-item has-text-centered\"><div><p class=\"heading\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var24 := `Categories`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"title\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var25 string
|
|
||||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(s.CategoriesCount)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 170, Col: 42}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div></nav><div class=\"section\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_Var19.Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = footer().Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = breadcrumbsLoad().Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func courseInfoElement(params CourseInfo) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var26 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var26 == nil {
|
|
||||||
templ_7745c5c3_Var26 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article class=\"column is-one-quarter\" hx-target=\"this\" hx-swap=\"outerHTML\"><div class=\"card\"><div class=\"card-image\"><figure class=\"image\"><img src=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(params.ImageLink))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></figure></div><div class=\"card-content\"><div class=\"media-content\"><p class=\"title is-5\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var27 string
|
|
||||||
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(params.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 193, Col: 40}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p class=\"subtitle is-8\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var28 := `oh well`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div><div class=\"content\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if params.FullPrice > 0 {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var29 string
|
|
||||||
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(params.FullPrice))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/kurious/ports/http/templ/list.templ`, Line: 198, Col: 41}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var30 := `руб.`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Var31 := `Бесплатно`
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = buttonRedirect(params.ID, "Show course", params.OriginLink).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></article>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
|
||||||
}
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
package templ
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestGetCompactedValue(t *testing.T) {
|
|
||||||
var tt = []struct {
|
|
||||||
name string
|
|
||||||
in int
|
|
||||||
exp string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "less than 1k",
|
|
||||||
in: 666,
|
|
||||||
exp: "666",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "exactly 1k",
|
|
||||||
in: 1000,
|
|
||||||
exp: "1k",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "some thousands",
|
|
||||||
in: 12345,
|
|
||||||
exp: "12k",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "more thousands",
|
|
||||||
in: 123456,
|
|
||||||
exp: "123k",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "million",
|
|
||||||
in: 1e6,
|
|
||||||
exp: "1m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "some millions",
|
|
||||||
in: 2e6,
|
|
||||||
exp: "2m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "more complex value",
|
|
||||||
in: 1.2346e6,
|
|
||||||
exp: "1.234m",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tt {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
got := getCompactedValue(tc.in)
|
|
||||||
if tc.exp != got {
|
|
||||||
t.Errorf("exp=%s got=%s", tc.exp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
{{ define "html_head" }}
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Courses Aggregator</title>
|
|
||||||
<script src="https://unpkg.com/htmx.org@1.8.0"></script>
|
|
||||||
<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
|
||||||
|
|
||||||
</head>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "header" }}
|
|
||||||
|
|
||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
Courses
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div id="navbarBasicExample" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item">
|
|
||||||
Home
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="navbar-item">
|
|
||||||
Find
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a class="navbar-link">
|
|
||||||
More
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<a class="navbar-item">
|
|
||||||
About
|
|
||||||
</a>
|
|
||||||
<a class="navbar-item">
|
|
||||||
Contact
|
|
||||||
</a>
|
|
||||||
<hr class="navbar-divider">
|
|
||||||
<a class="navbar-item">
|
|
||||||
Report an issue
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "footer" }}
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
Here will be footer
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
{{ define "course_info" }}
|
|
||||||
<article class="column is-one-quarter" hx-target="this" hx-swap="outerHTML">
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<img src="{{ .ImageLink }}">
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-5" onclick="location.href='{{ .OriginLink }}'">{{ .Name }}</p>
|
|
||||||
<p class="subtitle is-8">{{ .Description }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{{ if .FullPrice }}
|
|
||||||
<p>{{ .FullPrice }} rub.</p>
|
|
||||||
{{ else }}
|
|
||||||
<p>Бесплатно</p>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="button" onclick="location.href='{{ .OriginLink }}'">
|
|
||||||
Show Course
|
|
||||||
</button>
|
|
||||||
<button class="button" hx-get="/courses/{{ .ID }}/editdesc">
|
|
||||||
Edit description
|
|
||||||
</button>
|
|
||||||
<!-- <button class="button" hx-get="/courses/{{ .ID }}/" hx-target="#course-info" hx-swap="innerHTML">
|
|
||||||
View course
|
|
||||||
</button> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "edit_description" }}
|
|
||||||
<form
|
|
||||||
hx-ext="json-enc"
|
|
||||||
hx-put="/courses/{{ .ID }}/description"
|
|
||||||
hx-target="this"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
>
|
|
||||||
<fieldset disabled>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">Name</label>
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" type="text" placeholder="Text input" value="{{ .Name }}">
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div class="field is-horizontal">
|
|
||||||
<div class="field-label is-normal">
|
|
||||||
<label class="label">Description</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field">
|
|
||||||
<div class="control">
|
|
||||||
<textarea class="textarea" name="description" placeholder="Description">{{ .Description }}</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>Full price: {{ .FullPrice }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<p class="control">
|
|
||||||
<button class="button is-primary is-link" hx-include="[name='description']">
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<button class="button is-light" hx-get="/courses/{{ .ID }}/short">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
{{define "courses"}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
{{ template "html_head" . }}
|
|
||||||
|
|
||||||
<body>
|
|
||||||
{{ template "header" . }}
|
|
||||||
|
|
||||||
<nav class="level">
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Courses</p>
|
|
||||||
<p class="title">10k</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Clients</p>
|
|
||||||
<p class="title">1m</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Categories</p>
|
|
||||||
<p class="title">1,024</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="heading">Likes</p>
|
|
||||||
<p class="title">Over 9m</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<div class="container block">
|
|
||||||
<nav class="breadcrumb" aria-label="breadcrumbs" itemprop="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
{{ if .LearningTypeName }}
|
|
||||||
<a href="/courses" itemprop="url">
|
|
||||||
<span itemprop="title">
|
|
||||||
Курсы
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{{ else }}
|
|
||||||
<a>
|
|
||||||
<span itemprop="title" itemprop="url">
|
|
||||||
Курсы
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{{ end }}
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{{ if .LearningTypeName }}
|
|
||||||
<li>
|
|
||||||
{{ if .CourseThematicName }}
|
|
||||||
<a href="/courses/{{.ActiveLearningType}}" itemprop="url">
|
|
||||||
<span itemprop="title">
|
|
||||||
{{ .LearningTypeName }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{{ else }}
|
|
||||||
<a>
|
|
||||||
<span itemprop="title" itemprop="url">
|
|
||||||
{{ .LearningTypeName }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{{ end }}
|
|
||||||
</li>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if .CourseThematicName }}
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
<span itemprop="title" itemprop="url">
|
|
||||||
{{ .CourseThematicName }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{ end }}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<form id="filter-form" class="columns">
|
|
||||||
<div class="select">
|
|
||||||
<select id="learning-type-filter" name="learning_type">
|
|
||||||
<option value="">Все направления</option>
|
|
||||||
{{ range $t := .AvailableLearningTypes }}
|
|
||||||
<option value="{{$t.ID}}" {{ if eq $t.ID $.ActiveLearningType }}selected{{ end }}>{{ $t.Name }}</option>
|
|
||||||
{{ end }}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ if .LearningTypeName }}
|
|
||||||
<div class="select">
|
|
||||||
<select id="course-thematic-filter" name="course_thematic">
|
|
||||||
<option value="">Все темы</option>
|
|
||||||
{{ range $t := .AvailableCourseThematics }}
|
|
||||||
<option value="{{$t.ID}}" {{ if eq $t.ID $.ActiveCourseThematic }}selected{{ end }}>{{ $t.Name }}</option>
|
|
||||||
{{ end }}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<button id="go-to-filter" class="button">Перейти</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="category-course-list" class="container">
|
|
||||||
{{ range $category := .Categories }}
|
|
||||||
<div class="box">
|
|
||||||
|
|
||||||
<div class="title is-3">
|
|
||||||
<a href="/courses/{{ $category.ID }}">
|
|
||||||
{{ $category.Name }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="subtitle is-6">
|
|
||||||
Some description about the learning category {{ $category.Description }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ range $subcategory := $category.Subcategories }}
|
|
||||||
<div class="box">
|
|
||||||
<div class="title is-4">
|
|
||||||
<a href="/courses/{{ $category.ID }}/{{ $subcategory.ID }}">
|
|
||||||
{{ $subcategory.Name }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="subtitle is-6">Some description about course thematics {{ $subcategory.Description }}</div>
|
|
||||||
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
{{ range $course := $subcategory.Courses }}
|
|
||||||
{{ template "course_info" $course }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<div id="course-info"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
|
||||||
<a class="pagination-previous">Previous</a>
|
|
||||||
<a class="pagination-next" href="/courses/?next={{ .NextPageToken }}&per_page=50">Next page</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{{ template "footer" . }}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const formFilterOnSubmit = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
const lt = document.getElementById('learning-type-filter')
|
|
||||||
const ct = document.getElementById('course-thematic-filter');
|
|
||||||
let out = '/courses';
|
|
||||||
if (lt != null && lt.value != '') {
|
|
||||||
out += '/' + lt.value;
|
|
||||||
}
|
|
||||||
if (ct != null && ct.value != '') {
|
|
||||||
out += '/' + ct.value;
|
|
||||||
}
|
|
||||||
document.location.assign(out);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const ff = document.getElementById('filter-form');
|
|
||||||
ff.addEventListener('submit', formFilterOnSubmit);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
||||||
@ -2,11 +2,13 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/config"
|
"git.loyso.art/frx/kurious/internal/common/config"
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/adapters"
|
"git.loyso.art/frx/kurious/internal/kurious/adapters"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/app"
|
"git.loyso.art/frx/kurious/internal/kurious/app"
|
||||||
"git.loyso.art/frx/kurious/internal/kurious/app/command"
|
"git.loyso.art/frx/kurious/internal/kurious/app/command"
|
||||||
@ -14,9 +16,19 @@ import (
|
|||||||
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RepositoryEngine uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
RepositoryEngineUnknown RepositoryEngine = iota
|
||||||
|
RepositoryEngineYDB
|
||||||
|
RepositoryEngineSqlite
|
||||||
|
)
|
||||||
|
|
||||||
type ApplicationConfig struct {
|
type ApplicationConfig struct {
|
||||||
LogConfig config.Log
|
LogConfig config.Log
|
||||||
YDB config.YDB
|
YDB config.YDB
|
||||||
|
Sqlite config.Sqlite
|
||||||
|
Engine RepositoryEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
@ -26,14 +38,32 @@ type Application struct {
|
|||||||
closers []io.Closer
|
closers []io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplication(ctx context.Context, cfg ApplicationConfig, mapper domain.CourseMapper) (Application, error) {
|
func NewApplication(ctx context.Context, cfg ApplicationConfig, mapper domain.CourseMapper) (out Application, err error) {
|
||||||
log := config.NewSLogger(cfg.LogConfig)
|
log := config.NewSLogger(cfg.LogConfig)
|
||||||
ydbConnection, err := adapters.NewYDBConnection(ctx, cfg.YDB, log.With(slog.String("db", "ydb")))
|
|
||||||
|
var repoCloser io.Closer
|
||||||
|
var courseadapter domain.CourseRepository
|
||||||
|
var organizationrepo domain.OrganizationRepository
|
||||||
|
switch cfg.Engine {
|
||||||
|
case RepositoryEngineSqlite:
|
||||||
|
sqliteConnection, err := adapters.NewSqliteConnection(ctx, cfg.Sqlite, log.With(slog.String("db", "sqlite")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Application{}, fmt.Errorf("making ydb connection: %w", err)
|
return Application{}, fmt.Errorf("making sqlite connection: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
courseadapter := ydbConnection.CourseRepository()
|
courseadapter = sqliteConnection.CourseRepository()
|
||||||
|
organizationrepo = sqliteConnection.Organization()
|
||||||
|
repoCloser = sqliteConnection
|
||||||
|
case RepositoryEngineYDB:
|
||||||
|
return Application{}, errors.New("ydb is no longer supported")
|
||||||
|
default:
|
||||||
|
return Application{}, errors.New("unable to decide which db engine to use")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapper.CollectCounts(ctx, courseadapter)
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogWithWarnError(ctx, log, err, "unable to properly collect counts")
|
||||||
|
}
|
||||||
|
|
||||||
application := app.Application{
|
application := app.Application{
|
||||||
Commands: app.Commands{
|
Commands: app.Commands{
|
||||||
@ -41,17 +71,24 @@ func NewApplication(ctx context.Context, cfg ApplicationConfig, mapper domain.Co
|
|||||||
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),
|
UpdateCourseDescription: command.NewUpdateCourseDescriptionHandler(courseadapter, log),
|
||||||
|
|
||||||
|
InsertOrganization: command.NewCreateOrganizationHandler(organizationrepo, log),
|
||||||
},
|
},
|
||||||
Queries: app.Queries{
|
Queries: app.Queries{
|
||||||
ListCourses: query.NewListCourseHandler(courseadapter, mapper, log),
|
ListCourses: query.NewListCourseHandler(courseadapter, mapper, log),
|
||||||
ListLearningTypes: query.NewListLearningTypesHandler(courseadapter, mapper, log),
|
ListLearningTypes: query.NewListLearningTypesHandler(courseadapter, mapper, log),
|
||||||
ListCourseThematics: query.NewListCourseThematicsHandler(courseadapter, mapper, log),
|
ListCourseThematics: query.NewListCourseThematicsHandler(courseadapter, mapper, log),
|
||||||
|
ListCourseStatistics: query.NewListCoursesStatsHandler(mapper, courseadapter, log),
|
||||||
GetCourse: query.NewGetCourseHandler(courseadapter, mapper, log),
|
GetCourse: query.NewGetCourseHandler(courseadapter, mapper, log),
|
||||||
|
|
||||||
|
ListOrganzations: query.NewListOrganizationsHandler(organizationrepo, log),
|
||||||
|
ListOrganizationsStats: query.NewListOrganizationsStatsHandler(organizationrepo, log),
|
||||||
|
GetOrganization: query.NewGetOrganizationHandler(organizationrepo, log),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out := Application{Application: application}
|
out = Application{Application: application}
|
||||||
out.closers = append(out.closers, ydbConnection)
|
out.closers = append(out.closers, repoCloser)
|
||||||
out.log = log
|
out.log = log
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|||||||
24
migrations/sqlite/001_initial.sql
Normal file
24
migrations/sqlite/001_initial.sql
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
CREATE TABLE courses (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
external_id TEXT,
|
||||||
|
source_type TEXT NOT NULL,
|
||||||
|
source_name TEXT,
|
||||||
|
course_thematic TEXT NOT NULL,
|
||||||
|
learning_type TEXT NOT NULL,
|
||||||
|
organization_id TEXT NOT NULL,
|
||||||
|
origin_link TEXT NOT NULL,
|
||||||
|
image_link TEXT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
full_price REAL NOT NULL,
|
||||||
|
discount REAL NOT NULL,
|
||||||
|
duration INTEGER NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
starts_at DATETIME,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at DATETIME
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_course_thematic ON courses (course_thematic);
|
||||||
|
CREATE INDEX idx_learning_type ON courses (learning_type);
|
||||||
|
CREATE INDEX idx_organization_id ON courses (organization_id);
|
||||||
19
migrations/sqlite/002_learning_and_organization.sql
Normal file
19
migrations/sqlite/002_learning_and_organization.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
CREATE TABLE learning_categories (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
logo TEXT NULL,
|
||||||
|
courses_count INT NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
create table organizations (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
external_id TEXT NULL,
|
||||||
|
alias TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
site TEXT NOT NULL,
|
||||||
|
logo TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at DATETIME NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_organization_external_id ON organizations (external_id);
|
||||||
18
migrations/sqlite/assets.go
Normal file
18
migrations/sqlite/assets.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.sql
|
||||||
|
var migrations embed.FS
|
||||||
|
|
||||||
|
func getMigrationEntries() ([]fs.DirEntry, error) {
|
||||||
|
entries, err := fs.ReadDir(migrations, ".")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
240
migrations/sqlite/migrator.go
Normal file
240
migrations/sqlite/migrator.go
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||||
|
)
|
||||||
|
|
||||||
|
type migrationUnit struct {
|
||||||
|
num int
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u migrationUnit) apply(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
content, err := fs.ReadFile(migrations, u.path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := string(content)
|
||||||
|
_, err = tx.ExecContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortMigrationUnit(lhs, rhs migrationUnit) int {
|
||||||
|
if lhs.num < rhs.num {
|
||||||
|
return -1
|
||||||
|
} else if lhs.num > rhs.num {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMigrations(ctx context.Context, db *sql.DB, log *slog.Logger) error {
|
||||||
|
items, err := getMigrationEntries()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
units := make([]migrationUnit, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
if item.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
itemName := item.Name()
|
||||||
|
splitted := strings.SplitN(itemName, "_", 2)
|
||||||
|
if len(splitted) != 2 {
|
||||||
|
return fmt.Errorf("bad number of parts, expected 2, got %d", len(splitted))
|
||||||
|
}
|
||||||
|
|
||||||
|
splittedNum, err := strconv.Atoi(splitted[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing migration number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if splittedNum < 1 {
|
||||||
|
return fmt.Errorf("migration number expected to be greater than 0, but got %d", splittedNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
unit := migrationUnit{
|
||||||
|
num: splittedNum,
|
||||||
|
name: strings.TrimSuffix(splitted[1], ".sql"),
|
||||||
|
path: path.Join(itemName),
|
||||||
|
}
|
||||||
|
|
||||||
|
xcontext.LogDebug(ctx, log, "found migration unit", slog.Any("unit", unit))
|
||||||
|
|
||||||
|
units = append(units, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(units, sortMigrationUnit)
|
||||||
|
|
||||||
|
mr := &metaRepository{
|
||||||
|
db: db,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
err = mr.prepare(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("preparing meta repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := mr.run(ctx, units...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("running transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
xcontext.LogInfo(ctx, log, "some new migrations has been applied", slog.Int("count", count))
|
||||||
|
} else {
|
||||||
|
xcontext.LogDebug(ctx, log, "no new migrations has been applied")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type metaRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
log *slog.Logger
|
||||||
|
lastAppliedNumber int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *metaRepository) run(ctx context.Context, units ...migrationUnit) (count int, err error) {
|
||||||
|
idx, found := slices.BinarySearchFunc(units, r.lastAppliedNumber, func(mu migrationUnit, i int) int {
|
||||||
|
return cmp.Compare(mu.num, i)
|
||||||
|
})
|
||||||
|
if !found && r.lastAppliedNumber > 0 {
|
||||||
|
return 0, fmt.Errorf("migration %d stored in meta was not found in provided migrations", r.lastAppliedNumber)
|
||||||
|
} else if r.lastAppliedNumber > 0 {
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
|
||||||
|
xcontext.LogDebug(
|
||||||
|
ctx, r.log,
|
||||||
|
"starting to apply migrations",
|
||||||
|
slog.Int("last_applied_migration", r.lastAppliedNumber),
|
||||||
|
slog.Int("next_migration_idx", idx),
|
||||||
|
)
|
||||||
|
|
||||||
|
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelDefault,
|
||||||
|
ReadOnly: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("starting transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
var errtx error
|
||||||
|
if err != nil {
|
||||||
|
xcontext.LogError(ctx, r.log, "rolling back migration changes due to error")
|
||||||
|
errtx = tx.Rollback()
|
||||||
|
} else {
|
||||||
|
xcontext.LogDebug(ctx, r.log, "commiting migration changes")
|
||||||
|
errtx = tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.Join(err, errtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := idx; i < len(units); i++ {
|
||||||
|
unit := units[i]
|
||||||
|
err = unit.apply(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("unable to apply migration %q: %w", unit.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.adjustMigrationApplied(ctx, tx, unit)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("storing migration process info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
xcontext.LogInfo(
|
||||||
|
ctx, r.log, "migration unit applied",
|
||||||
|
slog.Int("number", unit.num),
|
||||||
|
slog.String("name", unit.name),
|
||||||
|
)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *metaRepository) prepare(ctx context.Context) error {
|
||||||
|
err := r.makeTable(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("making table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.loadLastAppliedMigration(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading last applied migration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *metaRepository) makeTable(ctx context.Context) error {
|
||||||
|
const query = `CREATE TABLE IF NOT EXISTS migration_meta (` +
|
||||||
|
` id INT PRIMARY KEY NOT NULL` +
|
||||||
|
`, name TEXT NOT NULL` +
|
||||||
|
`, applied_at INT NOT NULL` +
|
||||||
|
`);`
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *metaRepository) loadLastAppliedMigration(ctx context.Context) error {
|
||||||
|
const query = `SELECT COALESCE(MAX(id), 0) FROM migration_meta;`
|
||||||
|
|
||||||
|
err := r.db.QueryRowContext(ctx, query).Scan(&r.lastAppliedNumber)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *metaRepository) adjustMigrationApplied(ctx context.Context, tx *sql.Tx, unit migrationUnit) error {
|
||||||
|
const query = `INSERT INTO migration_meta (id, name, applied_at) VALUES (?, ?, ?)`
|
||||||
|
|
||||||
|
args := []any{
|
||||||
|
unit.num,
|
||||||
|
unit.name,
|
||||||
|
time.Now().Truncate(time.Second).Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := tx.ExecContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.lastAppliedNumber = unit.num
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
331
tags
331
tags
@ -1,331 +0,0 @@
|
|||||||
!_TAG_EXTRA_DESCRIPTION anonymous /Include tags for non-named objects like lambda/
|
|
||||||
!_TAG_EXTRA_DESCRIPTION fileScope /Include tags of file scope/
|
|
||||||
!_TAG_EXTRA_DESCRIPTION pseudo /Include pseudo tags/
|
|
||||||
!_TAG_EXTRA_DESCRIPTION subparser /Include tags generated by subparsers/
|
|
||||||
!_TAG_FIELD_DESCRIPTION epoch /the last modified time of the input file (only for F\/file kind tag)/
|
|
||||||
!_TAG_FIELD_DESCRIPTION file /File-restricted scoping/
|
|
||||||
!_TAG_FIELD_DESCRIPTION input /input file/
|
|
||||||
!_TAG_FIELD_DESCRIPTION name /tag name/
|
|
||||||
!_TAG_FIELD_DESCRIPTION pattern /pattern/
|
|
||||||
!_TAG_FIELD_DESCRIPTION typeref /Type and name of a variable or typedef/
|
|
||||||
!_TAG_FIELD_DESCRIPTION!Go package /the real package specified by the package name/
|
|
||||||
!_TAG_FIELD_DESCRIPTION!Go packageName /the name for referring the package/
|
|
||||||
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
|
||||||
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
|
||||||
!_TAG_KIND_DESCRIPTION!DTD E,entity /entities/
|
|
||||||
!_TAG_KIND_DESCRIPTION!DTD a,attribute /attributes/
|
|
||||||
!_TAG_KIND_DESCRIPTION!DTD e,element /elements/
|
|
||||||
!_TAG_KIND_DESCRIPTION!DTD n,notation /notations/
|
|
||||||
!_TAG_KIND_DESCRIPTION!DTD p,parameterEntity /parameter entities/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go M,anonMember /struct anonymous members/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go P,packageName /name for specifying imported package/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go Y,unknown /unknown/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go a,talias /type aliases/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go c,const /constants/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go f,func /functions/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go i,interface /interfaces/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go m,member /struct members/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go n,methodSpec /interface method specification/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go p,package /packages/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go s,struct /structs/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go t,type /types/
|
|
||||||
!_TAG_KIND_DESCRIPTION!Go v,var /variables/
|
|
||||||
!_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/
|
|
||||||
!_TAG_OUTPUT_FILESEP slash /slash or backslash/
|
|
||||||
!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
|
|
||||||
!_TAG_OUTPUT_VERSION 0.0 /current.age/
|
|
||||||
!_TAG_PARSER_VERSION!DTD 0.0 /current.age/
|
|
||||||
!_TAG_PARSER_VERSION!Go 0.0 /current.age/
|
|
||||||
!_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/
|
|
||||||
!_TAG_PROC_CWD /home/pi/go/src/git.loyso.art/frx/kurious/ //
|
|
||||||
!_TAG_PROGRAM_AUTHOR Universal Ctags Team //
|
|
||||||
!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
|
|
||||||
!_TAG_PROGRAM_URL https://ctags.io/ /official site/
|
|
||||||
!_TAG_PROGRAM_VERSION 6.0.0 /c480d71e/
|
|
||||||
!_TAG_ROLE_DESCRIPTION!DTD!element attOwner /attributes owner/
|
|
||||||
!_TAG_ROLE_DESCRIPTION!DTD!parameterEntity condition /conditions/
|
|
||||||
!_TAG_ROLE_DESCRIPTION!DTD!parameterEntity elementName /element names/
|
|
||||||
!_TAG_ROLE_DESCRIPTION!DTD!parameterEntity partOfAttDef /part of attribute definition/
|
|
||||||
!_TAG_ROLE_DESCRIPTION!Go!package imported /imported package/
|
|
||||||
!_TAG_ROLE_DESCRIPTION!Go!unknown receiverType /receiver type/
|
|
||||||
APIGatewayURL internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ APIGatewayURL string `json:"apiGatewayUrl"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
Address internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Address string `json:"address"`$/;" m struct:sravni.Contacts typeref:typename:string
|
|
||||||
Advertising internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Advertising CourseAdvertising `json:"advertising"`$/;" m struct:sravni.Course typeref:typename:CourseAdvertising
|
|
||||||
AdvertisingOnly internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ AdvertisingOnly bool `json:"advertisingOnly"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:bool
|
|
||||||
Alias internal/domain/kurious/kurious.go /^ Alias string$/;" m struct:kurious.Organization typeref:typename:string
|
|
||||||
Alias internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Alias string `json:"alias"`$/;" m struct:sravni.Organization typeref:typename:string
|
|
||||||
Alias internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Alias string `json:"alias"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:string
|
|
||||||
Approved internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Approved int `json:"approved"`$/;" m struct:sravni.RatingsInfo typeref:typename:int
|
|
||||||
BrandingURL internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ BrandingURL string `json:"brandingUrl"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
BuildID internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ BuildID string `json:"buildId"`$/;" m struct:sravni.PageState typeref:typename:string
|
|
||||||
BuildTime kurious.go /^func BuildTime() time.Time {$/;" f package:kurious typeref:typename:time.Time
|
|
||||||
ButtonMobileText internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ButtonMobileText string `json:"buttonMobileText"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
ButtonText internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ButtonText string `json:"buttonText"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
Categories internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Categories struct {$/;" m struct:sravni.InitialReduxState typeref:typename:struct { Data map[string]int `json:"data"`; }
|
|
||||||
Client internal/infrastructure/interfaceadapters/courses/sravni/client.go /^type Client interface {$/;" i package:sravni
|
|
||||||
Clone internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^func (p *PageState) Clone() *PageState {$/;" f struct:sravni.PageState typeref:typename:*PageState
|
|
||||||
Commit kurious.go /^func Commit() string {$/;" f package:kurious typeref:typename:string
|
|
||||||
ComplexCalculatedRatingValue internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ComplexCalculatedRatingValue float64 `json:"complexCalculatedRatingValue"`$/;" m struct:sravni.RatingsInfo typeref:typename:float64
|
|
||||||
Contacts internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Contacts Contacts `json:"contacts"`$/;" m struct:sravni.Organization typeref:typename:Contacts
|
|
||||||
Contacts internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type Contacts struct {$/;" s package:sravni
|
|
||||||
Cost internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Cost float64 `json:"cost"`$/;" m struct:sravni.CourseAdvertising typeref:typename:float64
|
|
||||||
Course internal/domain/kurious/kurious.go /^type Course struct {$/;" s package:kurious
|
|
||||||
Course internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type Course struct {$/;" s package:sravni
|
|
||||||
CourseAdvertising internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type CourseAdvertising struct {$/;" s package:sravni
|
|
||||||
CourseDiscount internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type CourseDiscount struct {$/;" s package:sravni
|
|
||||||
CourseImage internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ CourseImage string `json:"courseImage"`$/;" m struct:sravni.Course typeref:typename:string
|
|
||||||
CoursesThematics internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ CoursesThematics []string `json:"coursesThematics"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:[]string
|
|
||||||
CoursesThematics internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ CoursesThematics string$/;" m struct:sravni.ListEducationProductsParams typeref:typename:string
|
|
||||||
Created internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Created time.Time `json:"created"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:time.Time
|
|
||||||
CreatedAt internal/domain/kurious/kurious.go /^ CreatedAt time.Time$/;" m struct:kurious.Course typeref:typename:time.Time
|
|
||||||
CreatedAt internal/domain/kurious/kurious.go /^ CreatedAt time.Time$/;" m struct:kurious.Organization typeref:typename:time.Time
|
|
||||||
Data internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Data struct {$/;" m struct:sravni.ReduxDictionaries typeref:typename:struct { CourseThematics ReduxDictionaryContainer `json:"coursesThematics"`; LearningType ReduxDictionaryContainer `json:"learningType"`; LearningTypeSelection ReduxDictionaryContainer `json:"learningTypeSelection"`; }
|
|
||||||
Data internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Data struct {$/;" m struct:sravni.ReduxMetadata typeref:typename:struct { Prefooter []ReduxStatePrefooterItem `json:"prefooter"`; }
|
|
||||||
DateStart internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ DateStart any `json:"dateStart"`$/;" m struct:sravni.Course typeref:typename:any
|
|
||||||
Debugf internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^func (l restyCtxLogger) Debugf(format string, v ...any) {$/;" f struct:sravni.restyCtxLogger
|
|
||||||
DeletedAt internal/domain/kurious/kurious.go /^ DeletedAt nullable.Value[time.Time]$/;" m struct:kurious.Course typeref:typename:nullable.Value
|
|
||||||
DeletedAt internal/domain/kurious/kurious.go /^ DeletedAt nullable.Value[time.Time]$/;" m struct:kurious.Organization typeref:typename:nullable.Value
|
|
||||||
Description internal/domain/kurious/kurious.go /^ Description string$/;" m struct:kurious.Course typeref:typename:string
|
|
||||||
Dialog internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Dialog string `json:"dialog"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
Dictionaries internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Dictionaries ReduxDictionaries `json:"dictionaries"`$/;" m struct:sravni.InitialReduxState typeref:typename:ReduxDictionaries
|
|
||||||
DictionaryFormatFilterNew internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ DictionaryFormatFilterNew []string `json:"dictionaryFormatFilterNew"`$/;" m struct:sravni.Course typeref:typename:[]string
|
|
||||||
DictionaryLevelFilterNew internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ DictionaryLevelFilterNew []string `json:"dictionaryLevelFilterNew"`$/;" m struct:sravni.Course typeref:typename:[]string
|
|
||||||
Discount internal/domain/kurious/kurious.go /^ Discount float64$/;" m struct:kurious.Course typeref:typename:float64
|
|
||||||
Discount internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Discount CourseDiscount `json:"discount"`$/;" m struct:sravni.Course typeref:typename:CourseDiscount
|
|
||||||
Duration internal/domain/kurious/kurious.go /^ Duration time.Duration$/;" m struct:kurious.Course typeref:typename:time.Duration
|
|
||||||
EducationURL internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ EducationURL string `json:"educationUrl"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
EndDate internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ EndDate time.Time `json:"endDate"`$/;" m struct:sravni.CourseDiscount typeref:typename:time.Time
|
|
||||||
EndTime internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ EndTime any `json:"endTime"`$/;" m struct:sravni.CourseDiscount typeref:typename:any
|
|
||||||
Environment internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Environment string `json:"environment"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
ErrClientNotInited internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ErrClientNotInited domain.SimpleError = "client was not inited"$/;" c package:sravni typeref:typename:domain.SimpleError
|
|
||||||
ErrNotImplemented internal/domain/error.go /^ ErrNotImplemented SimpleError = "not implemented"$/;" c package:domain typeref:typename:SimpleError
|
|
||||||
ErrUnexpectedStatus internal/domain/error.go /^ ErrUnexpectedStatus SimpleError = "unexpected status"$/;" c package:domain typeref:typename:SimpleError
|
|
||||||
Error internal/domain/error.go /^func (err *ValidationError) Error() string {$/;" f struct:domain.ValidationError typeref:typename:string
|
|
||||||
Error internal/domain/error.go /^func (err SimpleError) Error() string {$/;" f type:domain.SimpleError typeref:typename:string
|
|
||||||
Errorf internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^func (l restyCtxLogger) Errorf(format string, v ...any) {$/;" f struct:sravni.restyCtxLogger
|
|
||||||
ExternalID internal/domain/kurious/kurious.go /^ ExternalID nullable.Value[string]$/;" m struct:kurious.Course typeref:typename:nullable.Value
|
|
||||||
ExternalID internal/domain/kurious/kurious.go /^ ExternalID nullable.Value[string]$/;" m struct:kurious.Organization typeref:typename:nullable.Value
|
|
||||||
Field internal/domain/error.go /^ Field string$/;" m struct:domain.ValidationError typeref:typename:string
|
|
||||||
Fields internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Fields []string `json:"fields"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:[]string
|
|
||||||
Fields internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Fields []field `json:"fields"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:[]field
|
|
||||||
Fingerprint internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Fingerprint string `json:"fingerPrint,omitempty"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
Full internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Full string `json:"full"`$/;" m struct:sravni.OrganizationName typeref:typename:string
|
|
||||||
FullPrice internal/domain/kurious/kurious.go /^ FullPrice float64$/;" m struct:kurious.Course typeref:typename:float64
|
|
||||||
Gateway internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Gateway string `json:"gatewayUrl"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
Genitive internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Genitive string `json:"genitive"`$/;" m struct:sravni.OrganizationName typeref:typename:string
|
|
||||||
GetMainPageState internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ GetMainPageState() *PageState$/;" n interface:sravni.Client typeref:typename:*PageState
|
|
||||||
GetMainPageState internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (c *client) GetMainPageState() *PageState {$/;" f struct:sravni.client typeref:typename:*PageState
|
|
||||||
HasOffersID internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ HasOffersID string `json:"hasOffersId"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
ID internal/domain/kurious/kurious.go /^ ID string$/;" m struct:kurious.Organization typeref:typename:string
|
|
||||||
ID internal/domain/kurious/kurious.go /^ ID string$/;" m struct:kurious.Course typeref:typename:string
|
|
||||||
ID internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ID string `json:"id"`$/;" m struct:sravni.Course typeref:typename:string
|
|
||||||
ID internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ID string `json:"id"`$/;" m struct:sravni.Organization typeref:typename:string
|
|
||||||
ID internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ID string `json:"_id"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:string
|
|
||||||
ImageLink internal/domain/kurious/kurious.go /^ ImageLink string$/;" m struct:kurious.Course typeref:typename:string
|
|
||||||
InitialReduxState internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ InitialReduxState InitialReduxState `json:"initialReduxState"`$/;" m struct:sravni.PageStateProperties typeref:typename:InitialReduxState
|
|
||||||
InitialReduxState internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type InitialReduxState struct {$/;" s package:sravni
|
|
||||||
IsLabsPartner internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ IsLabsPartner bool `json:"isLabsPartner"`$/;" m struct:sravni.Organization typeref:typename:bool
|
|
||||||
IsMix internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ IsMix bool `json:"isMix"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:bool
|
|
||||||
IsPartner internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ IsPartner bool `json:"isPartner"`$/;" m struct:sravni.CourseAdvertising typeref:typename:bool
|
|
||||||
IsTermApproximately internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ IsTermApproximately bool `json:"isTermApproximately"`$/;" m struct:sravni.Course typeref:typename:bool
|
|
||||||
Items internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Items []Course `json:"items"`$/;" m struct:sravni.ListEducationProductsResponse typeref:typename:[]Course
|
|
||||||
Keywords internal/domain/kurious/kurious.go /^ Keywords []string$/;" m struct:kurious.Course typeref:typename:[]string
|
|
||||||
LabelText internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ LabelText string `json:"labelText"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
LearningType internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ LearningType []string `json:"learningtype"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:[]string
|
|
||||||
LearningType internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ LearningType string$/;" m struct:sravni.ListEducationProductsParams typeref:typename:string
|
|
||||||
Learningtype internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Learningtype []string `json:"learningtype"`$/;" m struct:sravni.Course typeref:typename:[]string
|
|
||||||
License internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ License string `json:"license"`$/;" m struct:sravni.Organization typeref:typename:string
|
|
||||||
Limit internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Limit int `json:"limit"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:int
|
|
||||||
Limit internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Limit int$/;" m struct:sravni.ListEducationProductsParams typeref:typename:int
|
|
||||||
Link internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Link string `json:"link"`$/;" m struct:sravni.Course typeref:typename:string
|
|
||||||
Link internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type Link struct {$/;" s package:sravni
|
|
||||||
Links internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Links []Link `json:"links"`$/;" m struct:sravni.ReduxStatePrefooterItem typeref:typename:[]Link
|
|
||||||
ListEducationProductsParams internal/infrastructure/interfaceadapters/courses/sravni/client.go /^type ListEducationProductsParams struct {$/;" s package:sravni
|
|
||||||
ListEducationProductsRequest internal/infrastructure/interfaceadapters/courses/sravni/client.go /^type ListEducationProductsRequest struct {$/;" s package:sravni
|
|
||||||
ListEducationProductsResponse internal/infrastructure/interfaceadapters/courses/sravni/client.go /^type ListEducationProductsResponse struct {$/;" s package:sravni
|
|
||||||
ListEducationalProducts internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ ListEducationalProducts($/;" n interface:sravni.Client typeref:typename:(result ListEducationProductsResponse, err error)
|
|
||||||
ListEducationalProducts internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (c *client) ListEducationalProducts($/;" f struct:sravni.client typeref:typename:(result ListEducationProductsResponse, err error)
|
|
||||||
Location internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Location string `json:"location"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
LogoLink internal/domain/kurious/kurious.go /^ LogoLink string$/;" m struct:kurious.Organization typeref:typename:string
|
|
||||||
Logotypes internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Logotypes struct {$/;" m struct:sravni.Organization typeref:typename:struct { Square string `json:"square"`; Web string `json:"web"`; Android string `json:"android"`; }
|
|
||||||
Map pkg/utilities/slices/map.go /^func Map[S any, E any](s []S, f func(S) E) []E {$/;" f package:slices typeref:typename:(s []S, f func(S) E) [
|
|
||||||
Metadata internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Metadata ReduxMetadata `json:"metadata"`$/;" m struct:sravni.InitialReduxState typeref:typename:ReduxMetadata
|
|
||||||
MixRepeated internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ MixRepeated bool `json:"mixRepeated"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:bool
|
|
||||||
Monetization internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Monetization struct {$/;" m struct:sravni.CourseAdvertising typeref:typename:struct { Pixels struct { Click string `json:"click"`; Display string `json:"display"`; } `json:"pixels"`; Kind string `json:"kind"`; }
|
|
||||||
Name internal/domain/kurious/kurious.go /^ Name string$/;" m struct:kurious.Organization typeref:typename:string
|
|
||||||
Name internal/domain/kurious/kurious.go /^ Name string$/;" m struct:kurious.Course typeref:typename:string
|
|
||||||
Name internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Name string `json:"name"`$/;" m struct:sravni.Course typeref:typename:string
|
|
||||||
Name internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Name OrganizationName `json:"name"`$/;" m struct:sravni.Organization typeref:typename:OrganizationName
|
|
||||||
Name internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Name string `json:"name"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:string
|
|
||||||
Name internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Name string `json:"name"`$/;" m struct:sravni.field typeref:typename:string
|
|
||||||
NewClient internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func NewClient(ctx context.Context, log *slog.Logger, debug bool) (c *client, err error) {$/;" f package:sravni typeref:typename:(c *client, err error)
|
|
||||||
NewServices internal/infrastructure/interfaceadapters/services.go /^func NewServices() Services {$/;" f package:adapters typeref:typename:Services
|
|
||||||
NewValidationError internal/domain/error.go /^func NewValidationError(field, reason string) *ValidationError {$/;" f package:domain typeref:typename:*ValidationError
|
|
||||||
NewValue internal/domain/nullable/value.go /^func NewValue[T any](value T) Value[T] {$/;" f package:nullable typeref:typename:(value T) Value
|
|
||||||
NewValuePtr internal/domain/nullable/value.go /^func NewValuePtr[T any](value *T) Value[T] {$/;" f package:nullable typeref:typename:(value *T) Value
|
|
||||||
NotB2B internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ NotB2B string `json:"not-b2b"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
NotSubIsWebinar internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ NotSubIsWebinar string `json:"not-sub-isWebinar"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
OfferHighlightColor internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ OfferHighlightColor string `json:"offerHighlightColor"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
OfferTypes internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ OfferTypes []string `json:"offerTypes"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:[]string
|
|
||||||
Offset internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Offset int `json:"offset"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:int
|
|
||||||
Offset internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Offset int$/;" m struct:sravni.ListEducationProductsParams typeref:typename:int
|
|
||||||
Organization internal/domain/kurious/kurious.go /^type Organization struct {$/;" s package:kurious
|
|
||||||
Organization internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Organization string `json:"organization"`$/;" m struct:sravni.Course typeref:typename:string
|
|
||||||
Organization internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type Organization struct {$/;" s package:sravni
|
|
||||||
OrganizationID internal/domain/kurious/kurious.go /^ OrganizationID string$/;" m struct:kurious.Course typeref:typename:string
|
|
||||||
OrganizationName internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type OrganizationName struct {$/;" s package:sravni
|
|
||||||
Organizations internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ Organizations map[string]Organization `json:"organizations"`$/;" m struct:sravni.ListEducationProductsResponse typeref:typename:map[string]Organization
|
|
||||||
OrgnazationURL internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ OrgnazationURL string `json:"organizationsUrl"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
OriginLink internal/domain/kurious/kurious.go /^ OriginLink string$/;" m struct:kurious.Course typeref:typename:string
|
|
||||||
Page internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Page string `json:"page"`$/;" m struct:sravni.PageState typeref:typename:string
|
|
||||||
PageState internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type PageState struct {$/;" s package:sravni
|
|
||||||
PageStateProperties internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type PageStateProperties struct {$/;" s package:sravni
|
|
||||||
PageStateRuntimeConfig internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type PageStateRuntimeConfig struct {$/;" s package:sravni
|
|
||||||
ParticipantsCount internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ParticipantsCount int `json:"participantsCount"`$/;" m struct:sravni.RatingsInfo typeref:typename:int
|
|
||||||
Percent internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Percent int `json:"percent"`$/;" m struct:sravni.CourseDiscount typeref:typename:int
|
|
||||||
Phone internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Phone []string `json:"phone"`$/;" m struct:sravni.Contacts typeref:typename:[]string
|
|
||||||
PhoneVerifierURL internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ PhoneVerifierURL string `json:"phoneVerifierUrl"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
Prepositional internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Prepositional string `json:"prepositional"`$/;" m struct:sravni.OrganizationName typeref:typename:string
|
|
||||||
Price internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Price int `json:"price"`$/;" m struct:sravni.Course typeref:typename:int
|
|
||||||
PriceAll internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ PriceAll int `json:"priceAll"`$/;" m struct:sravni.Course typeref:typename:int
|
|
||||||
PriceInstallment internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ PriceInstallment int `json:"priceInstallment"`$/;" m struct:sravni.Course typeref:typename:int
|
|
||||||
ProductName internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ ProductName string `json:"productName,omitempty"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
PromoCode internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ PromoCode string `json:"promoCode"`$/;" m struct:sravni.CourseDiscount typeref:typename:string
|
|
||||||
PromoCodeType internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ PromoCodeType string `json:"promoCodeType"`$/;" m struct:sravni.CourseDiscount typeref:typename:string
|
|
||||||
Props internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Props PageStateProperties `json:"props"`$/;" m struct:sravni.PageState typeref:typename:PageStateProperties
|
|
||||||
Query internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Query map[string]string `json:"query"`$/;" m struct:sravni.PageState typeref:typename:map[string]string
|
|
||||||
RatingsInfo internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ RatingsInfo RatingsInfo `json:"ratingsInfo"`$/;" m struct:sravni.Organization typeref:typename:RatingsInfo
|
|
||||||
RatingsInfo internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type RatingsInfo struct {$/;" s package:sravni
|
|
||||||
Reason internal/domain/error.go /^ Reason string$/;" m struct:domain.ValidationError typeref:typename:string
|
|
||||||
ReduxDictionaries internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type ReduxDictionaries struct {$/;" s package:sravni
|
|
||||||
ReduxDictionaryContainer internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type ReduxDictionaryContainer struct {$/;" s package:sravni
|
|
||||||
ReduxMetadata internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type ReduxMetadata struct {$/;" s package:sravni
|
|
||||||
ReduxStatePrefooterItem internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type ReduxStatePrefooterItem struct {$/;" s package:sravni
|
|
||||||
Release internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Release string `json:"release"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
RuntimeConfig internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ RuntimeConfig PageStateRuntimeConfig `json:"runtimeConfig"`$/;" m struct:sravni.PageState typeref:typename:PageStateRuntimeConfig
|
|
||||||
ServiceName internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ ServiceName string `json:"serviceName"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
Services internal/infrastructure/interfaceadapters/services.go /^type Services struct{}$/;" s package:adapters
|
|
||||||
Set internal/domain/nullable/value.go /^func (n *Value[T]) Set(value T) {$/;" f unknown:nullable.T
|
|
||||||
Short internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Short string `json:"short"`$/;" m struct:sravni.OrganizationName typeref:typename:string
|
|
||||||
SideBarBannerText internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ SideBarBannerText string `json:"sideBarBannerText"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
SimpleError internal/domain/error.go /^type SimpleError string$/;" t package:domain typeref:typename:string
|
|
||||||
Site internal/domain/kurious/kurious.go /^ Site string$/;" m struct:kurious.Organization typeref:typename:string
|
|
||||||
SortDirection internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ SortDirection string `json:"sortDirection"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
SortProperty internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ SortProperty string `json:"sortProperty"`$/;" m struct:sravni.ListEducationProductsRequest typeref:typename:string
|
|
||||||
SourceName internal/domain/kurious/kurious.go /^ SourceName nullable.Value[string]$/;" m struct:kurious.Course typeref:typename:nullable.Value
|
|
||||||
SourceType internal/domain/kurious/kurious.go /^ SourceType SourceType$/;" m struct:kurious.Course typeref:typename:SourceType
|
|
||||||
SourceType internal/domain/kurious/kurious.go /^type SourceType uint8$/;" t package:kurious typeref:typename:uint8
|
|
||||||
SourceTypeManual internal/domain/kurious/kurious.go /^ SourceTypeManual$/;" c package:kurious
|
|
||||||
SourceTypeParsed internal/domain/kurious/kurious.go /^ SourceTypeParsed$/;" c package:kurious
|
|
||||||
SourceTypeUnset internal/domain/kurious/kurious.go /^ SourceTypeUnset SourceType = iota$/;" c package:kurious typeref:type:SourceType
|
|
||||||
StartsAt internal/domain/kurious/kurious.go /^ StartsAt time.Time$/;" m struct:kurious.Course typeref:typename:time.Time
|
|
||||||
TimeAllDay internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ TimeAllDay any `json:"timeAllDay"`$/;" m struct:sravni.Course typeref:typename:any
|
|
||||||
TimeAllHour internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ TimeAllHour any `json:"timeAllHour"`$/;" m struct:sravni.Course typeref:typename:any
|
|
||||||
TimeAllMonth internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ TimeAllMonth int `json:"timeAllMonth"`$/;" m struct:sravni.Course typeref:typename:int
|
|
||||||
TimeStart internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ TimeStart any `json:"timeStart"`$/;" m struct:sravni.Course typeref:typename:any
|
|
||||||
Title internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Title string `json:"title"`$/;" m struct:sravni.Link typeref:typename:string
|
|
||||||
Title internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Title string `json:"title"`$/;" m struct:sravni.ReduxStatePrefooterItem typeref:typename:string
|
|
||||||
Token internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Token []struct {$/;" m struct:sravni.CourseAdvertising typeref:typename:[]struct { ID string `json:"_id"`; Token []string `json:"token"`; Updated time.Time `json:"updated"`; V int `json:"__v"`; }
|
|
||||||
TotalCount internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ TotalCount int `json:"totalCount"`$/;" m struct:sravni.ListEducationProductsResponse typeref:typename:int
|
|
||||||
TotalCountAdv internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ TotalCountAdv int `json:"totalCountAdv"`$/;" m struct:sravni.ListEducationProductsResponse typeref:typename:int
|
|
||||||
TrackingType internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ TrackingType string `json:"trackingType"`$/;" m struct:sravni.CourseAdvertising typeref:typename:string
|
|
||||||
URL internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ URL string `json:"url"`$/;" m struct:sravni.Link typeref:typename:string
|
|
||||||
Updated internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Updated time.Time `json:"updated"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:time.Time
|
|
||||||
UpdatedAt internal/domain/kurious/kurious.go /^ UpdatedAt time.Time$/;" m struct:kurious.Course typeref:typename:time.Time
|
|
||||||
UpdatedAt internal/domain/kurious/kurious.go /^ UpdatedAt time.Time$/;" m struct:kurious.Organization typeref:typename:time.Time
|
|
||||||
UserID internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ UserID string `json:"userId"`$/;" m struct:sravni.ReduxDictionaryContainer typeref:typename:string
|
|
||||||
Valid internal/domain/nullable/value.go /^func (n Value[T]) Valid() bool {$/;" f unknown:nullable.T typeref:typename:bool
|
|
||||||
ValidationError internal/domain/error.go /^type ValidationError struct {$/;" s package:domain
|
|
||||||
Value internal/domain/nullable/value.go /^func (n Value[T]) Value() T {$/;" f unknown:nullable.T typeref:typename:T
|
|
||||||
Value internal/domain/nullable/value.go /^type Value[T any] struct {$/;" t package:nullable typeref:typename:[T any] struct { value T; valid bool;}
|
|
||||||
Value internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ Value string `json:"value"`$/;" m struct:sravni.field typeref:typename:string
|
|
||||||
Values internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (qs querySet) Values() []string {$/;" f struct:sravni.querySet typeref:typename:[]string
|
|
||||||
ValutPtr internal/domain/nullable/value.go /^func (n Value[T]) ValutPtr() *T {$/;" f unknown:nullable.T typeref:typename:*T
|
|
||||||
Version kurious.go /^func Version() string {$/;" f package:kurious typeref:typename:string
|
|
||||||
Warnf internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^func (l restyCtxLogger) Warnf(format string, v ...any) {$/;" f struct:sravni.restyCtxLogger
|
|
||||||
WebPath internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ WebPath string `json:"webPath"`$/;" m struct:sravni.PageStateRuntimeConfig typeref:typename:string
|
|
||||||
WithoutDiscountPrice internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^ WithoutDiscountPrice int `json:"withoutDiscountPrice"`$/;" m struct:sravni.Course typeref:typename:int
|
|
||||||
action cmd/dev/sravnicli/products.go /^type action interface {$/;" i package:main
|
|
||||||
adapters internal/infrastructure/interfaceadapters/services.go /^package adapters$/;" p
|
|
||||||
app cmd/dev/sravnicli/main.go /^func app(ctx context.Context, log *slog.Logger) (exitCode int, err error) {$/;" f package:main typeref:typename:(exitCode int, err error)
|
|
||||||
asCLIAction cmd/dev/sravnicli/products.go /^func asCLIAction(a action) cli.Action {$/;" f package:main typeref:typename:cli.Action
|
|
||||||
baseAction cmd/dev/sravnicli/products.go /^ *baseAction$/;" M struct:main.listProductsAction typeref:typename:*baseAction
|
|
||||||
baseAction cmd/dev/sravnicli/products.go /^type baseAction struct {$/;" s package:main
|
|
||||||
baseURL internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ baseURL = "https:\/\/www.sravni.ru\/kursy"$/;" c package:sravni
|
|
||||||
buildTime kurious.go /^ buildTime = ""$/;" v package:kurious
|
|
||||||
buildTimeParseOnce kurious.go /^var buildTimeParseOnce sync.Once$/;" v package:kurious typeref:typename:sync.Once
|
|
||||||
buildTimeParsed kurious.go /^ buildTimeParsed = time.Time{}$/;" v package:kurious
|
|
||||||
cachedMainPageInfo internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ cachedMainPageInfo *PageState$/;" m struct:sravni.client typeref:typename:*PageState
|
|
||||||
checkClientInited internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (c *client) checkClientInited() error {$/;" f struct:sravni.client typeref:typename:error
|
|
||||||
client cmd/dev/sravnicli/products.go /^ client sravni.Client$/;" m struct:main.baseAction typeref:typename:sravni.Client
|
|
||||||
client internal/infrastructure/interfaceadapters/courses/sravni/client.go /^type client struct {$/;" s package:sravni
|
|
||||||
commit kurious.go /^ commit = "unknown"$/;" v package:kurious
|
|
||||||
context cmd/dev/sravnicli/products.go /^ context() context.Context$/;" n interface:main.action typeref:typename:context.Context
|
|
||||||
context cmd/dev/sravnicli/products.go /^func (ba baseAction) context() context.Context {$/;" f struct:main.baseAction typeref:typename:context.Context
|
|
||||||
courseThematic cmd/dev/sravnicli/products.go /^ courseThematic string$/;" m struct:main.listProductsActionParams typeref:typename:string
|
|
||||||
courseThematicOptName cmd/dev/sravnicli/products.go /^ courseThematicOptName = "course_thematic"$/;" c package:main
|
|
||||||
courses internal/app/courses/client.go /^package courses$/;" p
|
|
||||||
ctx cmd/dev/sravnicli/products.go /^ ctx context.Context$/;" m struct:main.baseAction typeref:typename:context.Context
|
|
||||||
ctx internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^ ctx context.Context$/;" m struct:sravni.restyCtxLogger typeref:typename:context.Context
|
|
||||||
debugOptName cmd/dev/sravnicli/core.go /^ debugOptName = "verbose"$/;" c package:main
|
|
||||||
defaultOutput cmd/dev/sravnicli/main.go /^var defaultOutput = os.Stdout$/;" v package:main
|
|
||||||
defaultProductFields internal/infrastructure/interfaceadapters/courses/sravni/client.go /^var defaultProductFields = must(educationProductFields.exactSubset($/;" v package:sravni
|
|
||||||
domain internal/domain/error.go /^package domain$/;" p
|
|
||||||
educationProductFields internal/infrastructure/interfaceadapters/courses/sravni/client.go /^var educationProductFields = newQuerySet($/;" v package:sravni
|
|
||||||
exactSubset internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (qs querySet) exactSubset(values ...string) ([]string, error) {$/;" f struct:sravni.querySet typeref:typename:([]string, error)
|
|
||||||
field internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^type field struct {$/;" s package:sravni
|
|
||||||
findNode internal/infrastructure/interfaceadapters/courses/sravni/helpers.go /^func findNode(parent *html.Node, eq func(*html.Node) (found, deeper bool)) *html.Node {$/;" f package:sravni typeref:typename:*html.Node
|
|
||||||
getMainPageState internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (c *client) getMainPageState(ctx context.Context) (*PageState, error) {$/;" f struct:sravni.client typeref:typename:(*PageState, error)
|
|
||||||
handle cmd/dev/sravnicli/products.go /^ handle() error$/;" n interface:main.action typeref:typename:error
|
|
||||||
handle cmd/dev/sravnicli/products.go /^func (a *listProductsAction) handle() error {$/;" f struct:main.listProductsAction typeref:typename:error
|
|
||||||
handle cmd/dev/sravnicli/products.go /^func (ba *baseAction) handle() error {$/;" f struct:main.baseAction typeref:typename:error
|
|
||||||
hasValue internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (qs querySet) hasValue(value string) bool {$/;" f struct:sravni.querySet typeref:typename:bool
|
|
||||||
http internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ http *resty.Client$/;" m struct:sravni.client typeref:typename:*resty.Client
|
|
||||||
jsonOptName cmd/dev/sravnicli/core.go /^ jsonOptName = "json"$/;" c package:main
|
|
||||||
kurious internal/domain/kurious/kurious.go /^package kurious$/;" p
|
|
||||||
kurious internal/domain/kurious/repository.go /^package kurious$/;" p
|
|
||||||
kurious kurious.go /^package kurious$/;" p
|
|
||||||
learningType cmd/dev/sravnicli/products.go /^ learningType string$/;" m struct:main.listProductsActionParams typeref:typename:string
|
|
||||||
learningTypeOptName cmd/dev/sravnicli/products.go /^ learningTypeOptName = "learning_type"$/;" c package:main
|
|
||||||
limit cmd/dev/sravnicli/products.go /^ limit int$/;" m struct:main.listProductsActionParams typeref:typename:int
|
|
||||||
limitOption cmd/dev/sravnicli/core.go /^var limitOption = cli.NewOption("limit", "Limits amount of items to return").WithType(cli.TypeIn/;" v package:main
|
|
||||||
listProductsAction cmd/dev/sravnicli/products.go /^type listProductsAction struct {$/;" s package:main
|
|
||||||
listProductsActionParams cmd/dev/sravnicli/products.go /^type listProductsActionParams struct {$/;" s package:main
|
|
||||||
log cmd/dev/sravnicli/products.go /^ log *slog.Logger$/;" m struct:main.baseAction typeref:typename:*slog.Logger
|
|
||||||
log internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ log *slog.Logger$/;" m struct:sravni.client typeref:typename:*slog.Logger
|
|
||||||
log internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^ log *slog.Logger$/;" m struct:sravni.restyCtxLogger typeref:typename:*slog.Logger
|
|
||||||
main cmd/cli/main.go /^func main() {$/;" f package:main
|
|
||||||
main cmd/cli/main.go /^package main$/;" p
|
|
||||||
main cmd/dev/sravnicli/core.go /^package main$/;" p
|
|
||||||
main cmd/dev/sravnicli/main.go /^func main() {$/;" f package:main
|
|
||||||
main cmd/dev/sravnicli/main.go /^package main$/;" p
|
|
||||||
main cmd/dev/sravnicli/products.go /^package main$/;" p
|
|
||||||
makeEducationURL internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (c *client) makeEducationURL(path string) string {$/;" f struct:sravni.client typeref:typename:string
|
|
||||||
makeLogger cmd/dev/sravnicli/core.go /^func makeLogger(options map[string]string) *slog.Logger {$/;" f package:main typeref:typename:*slog.Logger
|
|
||||||
makeSravniClient cmd/dev/sravnicli/core.go /^func makeSravniClient(ctx context.Context, log *slog.Logger, options map[string]string) (sravni./;" f package:main typeref:typename:(sravni.Client, error)
|
|
||||||
mappedValues internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ mappedValues map[string]struct{}$/;" m struct:sravni.querySet typeref:typename:map[string]struct{}
|
|
||||||
must internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func must[T any](t T, err error) T {$/;" f package:sravni typeref:typename:(t T, err error) T
|
|
||||||
newBaseAction cmd/dev/sravnicli/products.go /^func newBaseAction(ctx context.Context) *baseAction {$/;" f package:main typeref:typename:*baseAction
|
|
||||||
newListProductAction cmd/dev/sravnicli/products.go /^func newListProductAction(ctx context.Context) cli.Action {$/;" f package:main typeref:typename:cli.Action
|
|
||||||
newQuerySet internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func newQuerySet(values ...string) querySet {$/;" f package:sravni typeref:typename:querySet
|
|
||||||
nullable internal/domain/nullable/value.go /^package nullable$/;" p
|
|
||||||
offset cmd/dev/sravnicli/products.go /^ offset int$/;" m struct:main.listProductsActionParams typeref:typename:int
|
|
||||||
offsetOption cmd/dev/sravnicli/core.go /^var offsetOption = cli.NewOption("offset", "Offsets items to return").WithType(cli.TypeInt)$/;" v package:main
|
|
||||||
params cmd/dev/sravnicli/products.go /^ params listProductsActionParams$/;" m struct:main.listProductsAction typeref:typename:listProductsActionParams
|
|
||||||
parse cmd/dev/sravnicli/products.go /^ parse(args []string, options map[string]string) error$/;" n interface:main.action typeref:typename:error
|
|
||||||
parse cmd/dev/sravnicli/products.go /^func (a *listProductsAction) parse(args []string, options map[string]string) error {$/;" f struct:main.listProductsAction typeref:typename:error
|
|
||||||
parse cmd/dev/sravnicli/products.go /^func (ba *baseAction) parse(_ []string, options map[string]string) (err error) {$/;" f struct:main.baseAction typeref:typename:(err error)
|
|
||||||
parsePageState internal/infrastructure/interfaceadapters/courses/sravni/client.go /^func (c *client) parsePageState(ctx context.Context, body io.Reader) (*PageState, error) {$/;" f struct:sravni.client typeref:typename:(*PageState, error)
|
|
||||||
querySet internal/infrastructure/interfaceadapters/courses/sravni/client.go /^type querySet struct {$/;" s package:sravni
|
|
||||||
restyCtxLogger internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^type restyCtxLogger struct {$/;" s package:sravni
|
|
||||||
setupAPICommand cmd/dev/sravnicli/products.go /^func setupAPICommand(ctx context.Context) cli.Command {$/;" f package:main typeref:typename:cli.Command
|
|
||||||
setupCLI cmd/dev/sravnicli/main.go /^func setupCLI(ctx context.Context) cli.App {$/;" f package:main typeref:typename:cli.App
|
|
||||||
slices pkg/utilities/slices/map.go /^package slices$/;" p
|
|
||||||
sravni internal/infrastructure/interfaceadapters/courses/sravni/client.go /^package sravni$/;" p
|
|
||||||
sravni internal/infrastructure/interfaceadapters/courses/sravni/entities.go /^package sravni$/;" p
|
|
||||||
sravni internal/infrastructure/interfaceadapters/courses/sravni/helpers.go /^package sravni$/;" p
|
|
||||||
sravni internal/infrastructure/interfaceadapters/courses/sravni/logger.go /^package sravni$/;" p
|
|
||||||
validCourseThematics internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ validCourseThematics querySet$/;" m struct:sravni.client typeref:typename:querySet
|
|
||||||
validLearningTypes internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ validLearningTypes querySet$/;" m struct:sravni.client typeref:typename:querySet
|
|
||||||
values internal/infrastructure/interfaceadapters/courses/sravni/client.go /^ values []string$/;" m struct:sravni.querySet typeref:typename:[]string
|
|
||||||
version kurious.go /^ version = "unknown"$/;" v package:kurious
|
|
||||||
Reference in New Issue
Block a user