Changed: DB Params
3
templ/examples/counter/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM pierrezemb/gostatic
|
||||
COPY ./public/ /srv/http/
|
||||
ENTRYPOINT ["/goStatic", "-port", "8080"]
|
25
templ/examples/counter/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## Tasks
|
||||
|
||||
### generate
|
||||
|
||||
```sh
|
||||
templ generate
|
||||
```
|
||||
|
||||
### deploy
|
||||
|
||||
requires: generate
|
||||
dir: cdk
|
||||
|
||||
```sh
|
||||
cdk deploy
|
||||
```
|
||||
|
||||
### deploy-hotswap
|
||||
|
||||
requires: generate
|
||||
dir: cdk
|
||||
|
||||
```sh
|
||||
cdk deploy --hotswap
|
||||
```
|
11851
templ/examples/counter/assets/css/bulma.css
vendored
Normal file
1
templ/examples/counter/assets/css/bulma.css.map
Normal file
1
templ/examples/counter/assets/css/bulma.min.css
vendored
Normal file
6
templ/examples/counter/assets/favicon/about.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v16/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
BIN
templ/examples/counter/assets/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
templ/examples/counter/assets/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
templ/examples/counter/assets/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
templ/examples/counter/assets/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
templ/examples/counter/assets/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 686 B |
BIN
templ/examples/counter/assets/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
1
templ/examples/counter/assets/favicon/site.webmanifest
Normal file
@@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/assets/favicon/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/assets/favicon/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
BIN
templ/examples/counter/assets/images/logo.png
Normal file
After Width: | Height: | Size: 10 KiB |
1
templ/examples/counter/assets/js/htmx.min.js
vendored
Normal file
19
templ/examples/counter/cdk/.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# go.sum should be committed
|
||||
!go.sum
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
47
templ/examples/counter/cdk/cdk.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"app": "go mod download && go run stack.go",
|
||||
"watch": {
|
||||
"include": [
|
||||
"**"
|
||||
],
|
||||
"exclude": [
|
||||
"README.md",
|
||||
"cdk*.json",
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
"**/*test.go"
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||
"@aws-cdk/core:checkSecretUsage": true,
|
||||
"@aws-cdk/core:target-partitions": [
|
||||
"aws",
|
||||
"aws-cn"
|
||||
],
|
||||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
|
||||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
|
||||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
|
||||
"@aws-cdk/aws-iam:minimizePolicies": true,
|
||||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
|
||||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
|
||||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
|
||||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
|
||||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
|
||||
"@aws-cdk/core:enablePartitionLiterals": true,
|
||||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
|
||||
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
|
||||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
|
||||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
|
||||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
|
||||
"@aws-cdk/aws-route53-patters:useCertificate": true,
|
||||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
|
||||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
|
||||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
|
||||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
|
||||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
|
||||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
|
||||
"@aws-cdk/aws-redshift:columnId": true,
|
||||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true
|
||||
}
|
||||
}
|
136
templ/examples/counter/cdk/stack.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awscloudfront"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awscloudfrontorigins"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awsiam"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awss3"
|
||||
"github.com/aws/aws-cdk-go/awscdk/v2/awss3deployment"
|
||||
awslambdago "github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2"
|
||||
"github.com/aws/constructs-go/constructs/v10"
|
||||
"github.com/aws/jsii-runtime-go"
|
||||
)
|
||||
|
||||
type CounterStackProps struct {
|
||||
awscdk.StackProps
|
||||
}
|
||||
|
||||
func NewCounterStack(scope constructs.Construct, id string, props *CounterStackProps) awscdk.Stack {
|
||||
var sprops awscdk.StackProps
|
||||
if props != nil {
|
||||
sprops = props.StackProps
|
||||
}
|
||||
stack := awscdk.NewStack(scope, &id, &sprops)
|
||||
|
||||
// Create a global count database.
|
||||
db := awsdynamodb.NewTable(stack, jsii.String("count"), &awsdynamodb.TableProps{
|
||||
PartitionKey: &awsdynamodb.Attribute{
|
||||
Name: jsii.String("_pk"),
|
||||
Type: awsdynamodb.AttributeType_STRING,
|
||||
},
|
||||
BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST,
|
||||
// Change this for production systems.
|
||||
RemovalPolicy: awscdk.RemovalPolicy_DESTROY,
|
||||
TimeToLiveAttribute: jsii.String("_ttl"),
|
||||
})
|
||||
|
||||
// Strip the binary, and remove the deprecated Lambda SDK RPC code for performance.
|
||||
// These options are not required, but make cold start faster.
|
||||
bundlingOptions := &awslambdago.BundlingOptions{
|
||||
GoBuildFlags: &[]*string{jsii.String(`-ldflags "-s -w" -tags lambda.norpc`)},
|
||||
}
|
||||
f := awslambdago.NewGoFunction(stack, jsii.String("handler"), &awslambdago.GoFunctionProps{
|
||||
Runtime: awslambda.Runtime_PROVIDED_AL2(),
|
||||
MemorySize: jsii.Number(1024),
|
||||
Architecture: awslambda.Architecture_ARM_64(),
|
||||
Entry: jsii.String("../lambda"),
|
||||
Bundling: bundlingOptions,
|
||||
Environment: &map[string]*string{
|
||||
"TABLE_NAME": db.TableName(),
|
||||
},
|
||||
})
|
||||
// Grant DB access.
|
||||
db.GrantReadWriteData(f)
|
||||
// Add a Function URL.
|
||||
lambdaURL := f.AddFunctionUrl(&awslambda.FunctionUrlOptions{
|
||||
AuthType: awslambda.FunctionUrlAuthType_NONE,
|
||||
})
|
||||
awscdk.NewCfnOutput(stack, jsii.String("lambdaFunctionUrl"), &awscdk.CfnOutputProps{
|
||||
ExportName: jsii.String("lambdaFunctionUrl"),
|
||||
Value: lambdaURL.Url(),
|
||||
})
|
||||
|
||||
assetsBucket := awss3.NewBucket(stack, jsii.String("assets"), &awss3.BucketProps{
|
||||
BlockPublicAccess: awss3.BlockPublicAccess_BLOCK_ALL(),
|
||||
Encryption: awss3.BucketEncryption_S3_MANAGED,
|
||||
EnforceSSL: jsii.Bool(true),
|
||||
RemovalPolicy: awscdk.RemovalPolicy_DESTROY,
|
||||
Versioned: jsii.Bool(false),
|
||||
})
|
||||
// Allow CloudFront to read from the bucket.
|
||||
cfOAI := awscloudfront.NewOriginAccessIdentity(stack, jsii.String("cfnOriginAccessIdentity"), &awscloudfront.OriginAccessIdentityProps{})
|
||||
cfs := awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{})
|
||||
cfs.AddActions(jsii.String("s3:GetBucket*"))
|
||||
cfs.AddActions(jsii.String("s3:GetObject*"))
|
||||
cfs.AddActions(jsii.String("s3:List*"))
|
||||
cfs.AddResources(assetsBucket.BucketArn())
|
||||
cfs.AddResources(jsii.String(fmt.Sprintf("%v/*", *assetsBucket.BucketArn())))
|
||||
cfs.AddCanonicalUserPrincipal(cfOAI.CloudFrontOriginAccessIdentityS3CanonicalUserId())
|
||||
assetsBucket.AddToResourcePolicy(cfs)
|
||||
|
||||
// Add a CloudFront distribution to route between the public directory and the Lambda function URL.
|
||||
lambdaURLDomain := awscdk.Fn_Select(jsii.Number(2), awscdk.Fn_Split(jsii.String("/"), lambdaURL.Url(), nil))
|
||||
lambdaOrigin := awscloudfrontorigins.NewHttpOrigin(lambdaURLDomain, &awscloudfrontorigins.HttpOriginProps{
|
||||
ProtocolPolicy: awscloudfront.OriginProtocolPolicy_HTTPS_ONLY,
|
||||
})
|
||||
cf := awscloudfront.NewDistribution(stack, jsii.String("customerFacing"), &awscloudfront.DistributionProps{
|
||||
DefaultBehavior: &awscloudfront.BehaviorOptions{
|
||||
AllowedMethods: awscloudfront.AllowedMethods_ALLOW_ALL(),
|
||||
Origin: lambdaOrigin,
|
||||
CachedMethods: awscloudfront.CachedMethods_CACHE_GET_HEAD(),
|
||||
OriginRequestPolicy: awscloudfront.OriginRequestPolicy_ALL_VIEWER_EXCEPT_HOST_HEADER(),
|
||||
CachePolicy: awscloudfront.CachePolicy_CACHING_DISABLED(),
|
||||
ViewerProtocolPolicy: awscloudfront.ViewerProtocolPolicy_REDIRECT_TO_HTTPS,
|
||||
},
|
||||
PriceClass: awscloudfront.PriceClass_PRICE_CLASS_100,
|
||||
})
|
||||
|
||||
// Add /assets* to the distribution backed by S3.
|
||||
assetsOrigin := awscloudfrontorigins.NewS3Origin(assetsBucket, &awscloudfrontorigins.S3OriginProps{
|
||||
// Get content from the / directory in the bucket.
|
||||
OriginPath: jsii.String("/"),
|
||||
OriginAccessIdentity: cfOAI,
|
||||
})
|
||||
cf.AddBehavior(jsii.String("/assets*"), assetsOrigin, nil)
|
||||
|
||||
// Export the domain.
|
||||
awscdk.NewCfnOutput(stack, jsii.String("cloudFrontDomain"), &awscdk.CfnOutputProps{
|
||||
ExportName: jsii.String("cloudfrontDomain"),
|
||||
Value: cf.DomainName(),
|
||||
})
|
||||
|
||||
// Deploy the contents of the ./assets directory to the S3 bucket.
|
||||
awss3deployment.NewBucketDeployment(stack, jsii.String("assetsDeployment"), &awss3deployment.BucketDeploymentProps{
|
||||
DestinationBucket: assetsBucket,
|
||||
Sources: &[]awss3deployment.ISource{
|
||||
awss3deployment.Source_Asset(jsii.String("../assets"), nil),
|
||||
},
|
||||
DestinationKeyPrefix: jsii.String("assets"),
|
||||
Distribution: cf,
|
||||
DistributionPaths: jsii.Strings("/assets*"),
|
||||
})
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer jsii.Close()
|
||||
app := awscdk.NewApp(nil)
|
||||
NewCounterStack(app, "CounterStack", &CounterStackProps{})
|
||||
app.Synth(nil)
|
||||
}
|
62
templ/examples/counter/components/components.templ
Normal file
@@ -0,0 +1,62 @@
|
||||
package components
|
||||
|
||||
import "strconv"
|
||||
|
||||
css border() {
|
||||
border: 1px solid #eeeeee;
|
||||
border-radius: 4px;
|
||||
margin: 10px;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
templ counts(global, session int) {
|
||||
<form id="countsForm" action="/" method="POST" hx-post="/" hx-select="#countsForm" hx-swap="outerHTML">
|
||||
<div class="columns">
|
||||
<div class={ "column", "has-text-centered", "is-primary", border }>
|
||||
<h1 class="title is-size-1 has-text-centered">{ strconv.Itoa(global) }</h1>
|
||||
<p class="subtitle has-text-centered">Global</p>
|
||||
<div><button class="button is-primary" type="submit" name="global" value="global">+1</button></div>
|
||||
</div>
|
||||
<div class={ "column", "has-text-centered", border }>
|
||||
<h1 class="title is-size-1 has-text-centered">{ strconv.Itoa(session) }</h1>
|
||||
<p class="subtitle has-text-centered">Session</p>
|
||||
<div><button class="button is-secondary" type="submit" name="session" value="session">+1</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
templ Page(global, session int) {
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Counts</title>
|
||||
<link rel="stylesheet" href="/assets/css/bulma.min.css"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/favicon/apple-touch-icon.png"/>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon/favicon-32x32.png"/>
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon/favicon-16x16.png"/>
|
||||
<link rel="manifest" href="/assets/favicon/site.webmanifest"/>
|
||||
<script src="/assets/js/htmx.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<header class="hero is-primary">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title">Counts</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-half">
|
||||
@counts(global, session)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
}
|
163
templ/examples/counter/components/components_templ.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "strconv"
|
||||
|
||||
func border() templ.CSSClass {
|
||||
templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
|
||||
templ_7745c5c3_CSSBuilder.WriteString(`border:1px solid #eeeeee;`)
|
||||
templ_7745c5c3_CSSBuilder.WriteString(`border-radius:4px;`)
|
||||
templ_7745c5c3_CSSBuilder.WriteString(`margin:10px;`)
|
||||
templ_7745c5c3_CSSBuilder.WriteString(`padding-top:30px;`)
|
||||
templ_7745c5c3_CSSBuilder.WriteString(`padding-bottom:30px;`)
|
||||
templ_7745c5c3_CSSID := templ.CSSID(`border`, templ_7745c5c3_CSSBuilder.String())
|
||||
return templ.ComponentCSSClass{
|
||||
ID: templ_7745c5c3_CSSID,
|
||||
Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
|
||||
}
|
||||
}
|
||||
|
||||
func counts(global, session int) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
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 = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form id=\"countsForm\" action=\"/\" method=\"POST\" hx-post=\"/\" hx-select=\"#countsForm\" hx-swap=\"outerHTML\"><div class=\"columns\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 = []any{"column", "has-text-centered", "is-primary", border}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div 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: `templ/examples/counter/components/components.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 = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><h1 class=\"title is-size-1 has-text-centered\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(global))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/examples/counter/components/components.templ`, Line: 17, Col: 72}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</h1><p class=\"subtitle has-text-centered\">Global</p><div><button class=\"button is-primary\" type=\"submit\" name=\"global\" value=\"global\">+1</button></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 = []any{"column", "has-text-centered", border}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/examples/counter/components/components.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"><h1 class=\"title is-size-1 has-text-centered\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(session))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/examples/counter/components/components.templ`, Line: 22, Col: 73}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</h1><p class=\"subtitle has-text-centered\">Session</p><div><button class=\"button is-secondary\" type=\"submit\" name=\"session\" value=\"session\">+1</button></div></div></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Page(global, session int) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var8 == nil {
|
||||
templ_7745c5c3_Var8 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Counts</title><link rel=\"stylesheet\" href=\"/assets/css/bulma.min.css\"><link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/assets/favicon/apple-touch-icon.png\"><link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/assets/favicon/favicon-32x32.png\"><link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/assets/favicon/favicon-16x16.png\"><link rel=\"manifest\" href=\"/assets/favicon/site.webmanifest\"><script src=\"/assets/js/htmx.min.js\"></script></head><body class=\"bg-gray-100\"><header class=\"hero is-primary\"><div class=\"hero-body\"><div class=\"container\"><h1 class=\"title\">Counts</h1></div></div></header><section class=\"section\"><div class=\"container\"><div class=\"columns is-centered\"><div class=\"column is-half\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = counts(global, session).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div></div></section></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
189
templ/examples/counter/db/db.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
)
|
||||
|
||||
type OptionsFunc func(*CountStore)
|
||||
|
||||
func WithClient(client *dynamodb.Client) func(*CountStore) {
|
||||
return func(ms *CountStore) {
|
||||
ms.db = client
|
||||
}
|
||||
}
|
||||
|
||||
func NewCountStore(tableName, region string, options ...OptionsFunc) (s *CountStore, err error) {
|
||||
s = &CountStore{
|
||||
tableName: tableName,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(s)
|
||||
}
|
||||
if s.db == nil {
|
||||
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.db = dynamodb.NewFromConfig(cfg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CountStore struct {
|
||||
db *dynamodb.Client
|
||||
tableName string
|
||||
}
|
||||
|
||||
func stripEmpty(strings []string) (op []string) {
|
||||
for _, s := range strings {
|
||||
if s != "" {
|
||||
op = append(op, s)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type countRecord struct {
|
||||
PK string `dynamodbav:"_pk"`
|
||||
Count int `dynamodbav:"count"`
|
||||
}
|
||||
|
||||
func (s CountStore) BatchGet(ctx context.Context, ids ...string) (counts []int, err error) {
|
||||
nonEmptyIDs := stripEmpty(ids)
|
||||
if len(nonEmptyIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Make DynamoDB keys.
|
||||
ris := make(map[string]types.KeysAndAttributes)
|
||||
for _, id := range nonEmptyIDs {
|
||||
ri := ris[s.tableName]
|
||||
ri.Keys = append(ris[s.tableName].Keys, map[string]types.AttributeValue{
|
||||
"_pk": &types.AttributeValueMemberS{
|
||||
Value: id,
|
||||
},
|
||||
})
|
||||
ri.ConsistentRead = aws.Bool(true)
|
||||
ris[s.tableName] = ri
|
||||
}
|
||||
|
||||
// Execute the batch request.
|
||||
var batchResponses []map[string]types.AttributeValue
|
||||
|
||||
// DynamoDB might not process everything, so we need a loop.
|
||||
var unprocessedAttempts int
|
||||
for {
|
||||
var bgio *dynamodb.BatchGetItemOutput
|
||||
bgio, err = s.db.BatchGetItem(ctx, &dynamodb.BatchGetItemInput{
|
||||
RequestItems: ris,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, responses := range bgio.Responses {
|
||||
batchResponses = append(batchResponses, responses...)
|
||||
}
|
||||
if len(bgio.UnprocessedKeys) > 0 {
|
||||
ris = bgio.UnprocessedKeys
|
||||
unprocessedAttempts++
|
||||
if unprocessedAttempts > 3 {
|
||||
err = fmt.Errorf("countstore: exceeded three attempts to get all counts")
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Process the responses into structs.
|
||||
crs := []countRecord{}
|
||||
err = attributevalue.UnmarshalListOfMaps(batchResponses, &crs)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("countstore: failed to unmarshal result of BatchGet: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Match up the inputs to the records.
|
||||
idToCount := make(map[string]int, len(ids))
|
||||
for _, cr := range crs {
|
||||
idToCount[cr.PK] = cr.Count
|
||||
}
|
||||
|
||||
// Create the output in the right order.
|
||||
// Missing values are defaulted to zero.
|
||||
for _, id := range ids {
|
||||
counts = append(counts, idToCount[id])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s CountStore) Get(ctx context.Context, id string) (count int, err error) {
|
||||
if id == "" {
|
||||
return
|
||||
}
|
||||
gio, err := s.db.GetItem(ctx, &dynamodb.GetItemInput{
|
||||
Key: map[string]types.AttributeValue{
|
||||
"_pk": &types.AttributeValueMemberS{
|
||||
Value: id,
|
||||
},
|
||||
},
|
||||
TableName: &s.tableName,
|
||||
ConsistentRead: aws.Bool(true),
|
||||
})
|
||||
if err != nil || gio.Item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var cr countRecord
|
||||
err = attributevalue.UnmarshalMap(gio.Item, &cr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("countstore: failed to process result of Get: %w", err)
|
||||
}
|
||||
count = cr.Count
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s CountStore) Increment(ctx context.Context, id string) (count int, err error) {
|
||||
if id == "" {
|
||||
return
|
||||
}
|
||||
uio, err := s.db.UpdateItem(ctx, &dynamodb.UpdateItemInput{
|
||||
Key: map[string]types.AttributeValue{
|
||||
"_pk": &types.AttributeValueMemberS{
|
||||
Value: id,
|
||||
},
|
||||
},
|
||||
TableName: &s.tableName,
|
||||
UpdateExpression: aws.String("SET #c = if_not_exists(#c, :zero) + :one"),
|
||||
ExpressionAttributeNames: map[string]string{
|
||||
"#c": "count",
|
||||
},
|
||||
ExpressionAttributeValues: map[string]types.AttributeValue{
|
||||
":zero": &types.AttributeValueMemberN{Value: "0"},
|
||||
":one": &types.AttributeValueMemberN{Value: "1"},
|
||||
},
|
||||
ReturnValues: types.ReturnValueAllNew,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the response.
|
||||
var cr countRecord
|
||||
err = attributevalue.UnmarshalMap(uio.Attributes, &cr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("countstore: failed to process result of Increment: %w", err)
|
||||
}
|
||||
count = cr.Count
|
||||
|
||||
return
|
||||
}
|
53
templ/examples/counter/go.mod
Normal file
@@ -0,0 +1,53 @@
|
||||
module github.com/a-h/templ/examples/counter
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.3
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.234-0.20230427112944-80f0dc03a8a8
|
||||
github.com/akrylysov/algnhsa v1.1.0
|
||||
github.com/aws/aws-cdk-go/awscdk/v2 v2.147.3
|
||||
github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2 v2.147.3-alpha.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.7
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.1
|
||||
github.com/aws/constructs-go/constructs/v10 v10.3.0
|
||||
github.com/aws/jsii-runtime-go v1.101.0
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/aws/aws-lambda-go v1.47.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.202 // indirect
|
||||
github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.2 // indirect
|
||||
github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.0.3 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/yuin/goldmark v1.7.4 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/a-h/templ => ../../
|
109
templ/examples/counter/go.sum
Normal file
@@ -0,0 +1,109 @@
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/akrylysov/algnhsa v1.1.0 h1:G0SoP16tMRyiism7VNc3JFA0wq/cVgEkp/ExMVnc6PQ=
|
||||
github.com/akrylysov/algnhsa v1.1.0/go.mod h1:+bOweRs/WBu5awl+ifCoSYAuKVPAmoTk8XOMrZ1xwiw=
|
||||
github.com/aws/aws-cdk-go/awscdk/v2 v2.147.3 h1:7Wbi5d1f+RGn6fg9YzzMjD5D/rc/62zuFOtmJIed+B0=
|
||||
github.com/aws/aws-cdk-go/awscdk/v2 v2.147.3/go.mod h1:WF3lt7ah4wNktbClICIBbKdITtCqyCrPBQl3nkaLug4=
|
||||
github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2 v2.147.3-alpha.0 h1:j+eT+oCMXTQZL+9ERI1FBDxkFApHPZTYZhtj9zalvlo=
|
||||
github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2 v2.147.3-alpha.0/go.mod h1:uUP2KJ0yXDOlAl5VmSN36cSpw5Ajv10JVhPl1M7WJ6A=
|
||||
github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI=
|
||||
github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.7 h1:pPhmvNKbgb9l5VHcPmMx9g+FHtRbY+ba2J6GefXQGEI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.7/go.mod h1:OZU7QRvIYXhKry99PttkDTQyN8yCo8RzYjhIKHdQXoo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.1 h1:Szwz1vpZkvfhFMJ0X5uUECgHeUmPAxk1UGqAVs/pARw=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.1/go.mod h1:b4wouGyJlzkr2HAvPrDGgYNp1EtmlXOkzhEOvl0c0FQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.1 h1:jfkCLx62YWL6bSOkT7aEDKNAX3OwWomlThCxQNBPvbY=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.1/go.mod h1:dLPiMfhRZhblwOeKqdNde7K9jl/pMuIGCGAwC6vQOIo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.14 h1:X1J0Kd17n1PeXeoArNXlvnKewCyMvhVQh7iNMy6oi3s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.14/go.mod h1:VYMN7l7dxp6xtQRjqIau6d7QAbmPG+yJ75GtCy70f18=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
|
||||
github.com/aws/constructs-go/constructs/v10 v10.3.0 h1:LsjBIMiaDX/vqrXWhzTquBJ9pPdi02/H+z1DCwg0PEM=
|
||||
github.com/aws/constructs-go/constructs/v10 v10.3.0/go.mod h1:GgzwIwoRJ2UYsr3SU+JhAl+gq5j39bEMYf8ev3J+s9s=
|
||||
github.com/aws/jsii-runtime-go v1.101.0 h1:x4rWNWRz7uDhVN0qSO7T6cG0VAhQ9300s5DjWUrXmWY=
|
||||
github.com/aws/jsii-runtime-go v1.101.0/go.mod h1:4L4Qmve/HSwM5hXV5ZowR2gBNb9zqkUtycaaN6aZ3mg=
|
||||
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
|
||||
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.202 h1:VixXB9DnHN8oP7pXipq8GVFPjWCOdeNxIaS/ZyUwTkI=
|
||||
github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.202/go.mod h1:iPUti/SWjA3XAS3CpnLciFjS8TN9Y+8mdZgDfSgcyus=
|
||||
github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.2 h1:k+WD+6cERd59Mao84v0QtRrcdZuuSMfzlEmuIypKnVs=
|
||||
github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.2/go.mod h1:CvFHBo0qcg8LUkJqIxQtP1rD/sNGv9bX3L2vHT2FUAo=
|
||||
github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.0.3 h1:8NLWOIVaxAtpUXv5reojlAeDP7R8yswm9mDONf7F/3o=
|
||||
github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv6/v2 v2.0.3/go.mod h1:ZjFqfhYpCLzh4z7ChcHCrkXfqCuEiRlNApDfJd6plts=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
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/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
|
||||
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
81
templ/examples/counter/handlers/default.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/a-h/templ/examples/counter/components"
|
||||
"github.com/a-h/templ/examples/counter/services"
|
||||
"github.com/a-h/templ/examples/counter/session"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
type CountService interface {
|
||||
Increment(ctx context.Context, it services.IncrementType, sessionID string) (counts services.Counts, err error)
|
||||
Get(ctx context.Context, sessionID string) (counts services.Counts, err error)
|
||||
}
|
||||
|
||||
func New(log *slog.Logger, cs CountService) *DefaultHandler {
|
||||
return &DefaultHandler{
|
||||
Log: log,
|
||||
CountService: cs,
|
||||
}
|
||||
}
|
||||
|
||||
type DefaultHandler struct {
|
||||
Log *slog.Logger
|
||||
CountService CountService
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
h.Post(w, r)
|
||||
return
|
||||
}
|
||||
h.Get(w, r)
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
var props ViewProps
|
||||
var err error
|
||||
props.Counts, err = h.CountService.Get(r.Context(), session.ID(r))
|
||||
if err != nil {
|
||||
h.Log.Error("failed to get counts", slog.Any("error", err))
|
||||
http.Error(w, "failed to get counts", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.View(w, r, props)
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) Post(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
// Decide the action to take based on the button that was pressed.
|
||||
var it services.IncrementType
|
||||
if r.Form.Has("global") {
|
||||
it = services.IncrementTypeGlobal
|
||||
}
|
||||
if r.Form.Has("session") {
|
||||
it = services.IncrementTypeSession
|
||||
}
|
||||
|
||||
counts, err := h.CountService.Increment(r.Context(), it, session.ID(r))
|
||||
if err != nil {
|
||||
h.Log.Error("failed to increment", slog.Any("error", err))
|
||||
http.Error(w, "failed to increment", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Display the view.
|
||||
h.View(w, r, ViewProps{
|
||||
Counts: counts,
|
||||
})
|
||||
}
|
||||
|
||||
type ViewProps struct {
|
||||
Counts services.Counts
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) View(w http.ResponseWriter, r *http.Request, props ViewProps) {
|
||||
components.Page(props.Counts.Global, props.Counts.Session).Render(r.Context(), w)
|
||||
}
|
30
templ/examples/counter/lambda/main.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/a-h/templ/examples/counter/db"
|
||||
"github.com/a-h/templ/examples/counter/handlers"
|
||||
"github.com/a-h/templ/examples/counter/services"
|
||||
"github.com/a-h/templ/examples/counter/session"
|
||||
"github.com/akrylysov/algnhsa"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create handlers.
|
||||
log := slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
||||
s, err := db.NewCountStore(os.Getenv("TABLE_NAME"), os.Getenv("AWS_REGION"))
|
||||
if err != nil {
|
||||
log.Error("failed to create store", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
cs := services.NewCount(log, s)
|
||||
h := handlers.New(log, cs)
|
||||
|
||||
// Add session middleware.
|
||||
sh := session.NewMiddleware(h)
|
||||
|
||||
// Start Lambda.
|
||||
algnhsa.ListenAndServe(sh, nil)
|
||||
}
|
43
templ/examples/counter/main.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/a-h/templ/examples/counter/db"
|
||||
"github.com/a-h/templ/examples/counter/handlers"
|
||||
"github.com/a-h/templ/examples/counter/services"
|
||||
"github.com/a-h/templ/examples/counter/session"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
||||
s, err := db.NewCountStore(os.Getenv("TABLE_NAME"), os.Getenv("AWS_REGION"))
|
||||
if err != nil {
|
||||
log.Error("failed to create store", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
cs := services.NewCount(log, s)
|
||||
h := handlers.New(log, cs)
|
||||
|
||||
var secureFlag = true
|
||||
if os.Getenv("SECURE_FLAG") == "false" {
|
||||
secureFlag = false
|
||||
}
|
||||
|
||||
// Add session middleware.
|
||||
sh := session.NewMiddleware(h, session.WithSecure(secureFlag))
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "localhost:9000",
|
||||
Handler: sh,
|
||||
ReadTimeout: time.Second * 10,
|
||||
WriteTimeout: time.Second * 10,
|
||||
}
|
||||
|
||||
fmt.Printf("Listening on %v\n", server.Addr)
|
||||
server.ListenAndServe()
|
||||
}
|
84
templ/examples/counter/services/count.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/a-h/templ/examples/counter/db"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
type Counts struct {
|
||||
Global int
|
||||
Session int
|
||||
}
|
||||
|
||||
type IncrementType int
|
||||
|
||||
const (
|
||||
IncrementTypeUnknown IncrementType = iota
|
||||
IncrementTypeGlobal
|
||||
IncrementTypeSession
|
||||
)
|
||||
|
||||
var ErrUnknownIncrementType error = errors.New("unknown increment type")
|
||||
|
||||
func NewCount(log *slog.Logger, cs *db.CountStore) Count {
|
||||
return Count{
|
||||
Log: log,
|
||||
CountStore: cs,
|
||||
}
|
||||
}
|
||||
|
||||
type Count struct {
|
||||
Log *slog.Logger
|
||||
CountStore *db.CountStore
|
||||
}
|
||||
|
||||
func (cs Count) Increment(ctx context.Context, it IncrementType, sessionID string) (counts Counts, err error) {
|
||||
// Work out which operations to do.
|
||||
var global, session func(ctx context.Context, id string) (count int, err error)
|
||||
switch it {
|
||||
case IncrementTypeGlobal:
|
||||
global = cs.CountStore.Increment
|
||||
session = cs.CountStore.Get
|
||||
case IncrementTypeSession:
|
||||
global = cs.CountStore.Get
|
||||
session = cs.CountStore.Increment
|
||||
default:
|
||||
return counts, ErrUnknownIncrementType
|
||||
}
|
||||
|
||||
// Run the operations in parallel.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
errs := make([]error, 2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
counts.Global, errs[0] = global(ctx, "global")
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
counts.Session, errs[1] = session(ctx, sessionID)
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
return counts, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (cs Count) Get(ctx context.Context, sessionID string) (counts Counts, err error) {
|
||||
globalAndSessionCounts, err := cs.CountStore.BatchGet(ctx, "global", sessionID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("countservice: failed to get counts: %w", err)
|
||||
return
|
||||
}
|
||||
if len(globalAndSessionCounts) != 2 {
|
||||
err = fmt.Errorf("countservice: unexpected counts returned, expected 2, got %d", len(globalAndSessionCounts))
|
||||
return
|
||||
}
|
||||
counts.Global = globalAndSessionCounts[0]
|
||||
counts.Session = globalAndSessionCounts[1]
|
||||
return
|
||||
}
|
56
templ/examples/counter/session/session.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
type MiddlewareOpts func(*Middleware)
|
||||
|
||||
func NewMiddleware(next http.Handler, opts ...MiddlewareOpts) http.Handler {
|
||||
mw := Middleware{
|
||||
Next: next,
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&mw)
|
||||
}
|
||||
return mw
|
||||
}
|
||||
|
||||
func WithSecure(secure bool) MiddlewareOpts {
|
||||
return func(m *Middleware) {
|
||||
m.Secure = secure
|
||||
}
|
||||
}
|
||||
|
||||
func WithHTTPOnly(httpOnly bool) MiddlewareOpts {
|
||||
return func(m *Middleware) {
|
||||
m.HTTPOnly = httpOnly
|
||||
}
|
||||
}
|
||||
|
||||
type Middleware struct {
|
||||
Next http.Handler
|
||||
Secure bool
|
||||
HTTPOnly bool
|
||||
}
|
||||
|
||||
func ID(r *http.Request) (id string) {
|
||||
cookie, err := r.Cookie("sessionID")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return cookie.Value
|
||||
}
|
||||
|
||||
func (mw Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
id := ID(r)
|
||||
if id == "" {
|
||||
id = ksuid.New().String()
|
||||
http.SetCookie(w, &http.Cookie{Name: "sessionID", Value: id, Secure: mw.Secure, HttpOnly: mw.HTTPOnly})
|
||||
}
|
||||
mw.Next.ServeHTTP(w, r)
|
||||
}
|