@@ -12,6 +12,7 @@ import (
12
12
"io/fs"
13
13
"net/http"
14
14
"net/url"
15
+ "os"
15
16
"path"
16
17
"slices"
17
18
"strconv"
@@ -177,7 +178,7 @@ func (a *Action) runAction(w http.ResponseWriter, r *http.Request) {
177
178
}
178
179
isHtmxRequest := r .Header .Get ("HX-Request" ) == "true"
179
180
180
- r .ParseForm ()
181
+ r .ParseMultipartForm ( 10 << 20 ) // 10 MB max file size
181
182
var err error
182
183
dryRun := false
183
184
dryRunStr := r .Form .Get ("dry-run" )
@@ -210,23 +211,71 @@ func (a *Action) runAction(w http.ResponseWriter, r *http.Request) {
210
211
211
212
qsParams := url.Values {}
212
213
214
+ var tempDir string
213
215
// Update args with submitted form values
214
216
for _ , param := range a .params {
215
- formValue := r .Form .Get (param .Name )
216
- if formValue == "" {
217
- if param .Type == starlark_type .BOOLEAN {
218
- // Form does not submit unchecked checkboxes, set to false
219
- args [param .Name ] = starlark .Bool (false )
220
- qsParams .Add (param .Name , "false" )
217
+ if a .hidden [param .Name ] {
218
+ continue
219
+ }
220
+
221
+ if param .DisplayType == apptype .DisplayTypeFileUpload {
222
+ f , fh , err := r .FormFile (param .Name )
223
+ if err == http .ErrMissingFile {
224
+ args [param .Name ] = starlark .String ("" )
225
+ continue
221
226
}
222
- } else {
223
- newVal , err := apptype .ParamStringToType (param .Name , param .Type , formValue )
227
+
228
+ if err != nil {
229
+ http .Error (w , fmt .Sprintf ("error getting file %s: %s" , param .Name , err ), http .StatusBadRequest )
230
+ return
231
+ }
232
+
233
+ if tempDir == "" {
234
+ tempDir , err = os .MkdirTemp ("" , "clace-file-upload-*" )
235
+ if err != nil {
236
+ http .Error (w , err .Error (), http .StatusInternalServerError )
237
+ return
238
+ }
239
+
240
+ defer func () {
241
+ if remErr := os .RemoveAll (tempDir ); remErr != nil {
242
+ a .Error ().Err (remErr ).Msg ("error removing temp dir" )
243
+ }
244
+ }()
245
+ }
246
+
247
+ fullPath := path .Join (tempDir , fh .Filename )
248
+ destFile , err := os .Create (fullPath )
224
249
if err != nil {
225
- http .Error (w , err .Error (), http .StatusBadRequest )
250
+ http .Error (w , err .Error (), http .StatusInternalServerError )
251
+ return
252
+ }
253
+ defer destFile .Close ()
254
+
255
+ // Write contents of uploaded file to destFile
256
+ if _ , err = io .Copy (destFile , f ); err != nil {
257
+ http .Error (w , err .Error (), http .StatusInternalServerError )
226
258
return
227
259
}
228
- args [param .Name ] = newVal
229
- qsParams .Add (param .Name , formValue )
260
+ args [param .Name ] = starlark .String (fullPath )
261
+ } else {
262
+ // Not file upload, regular param
263
+ formValue := r .Form .Get (param .Name )
264
+ if formValue == "" {
265
+ if param .Type == starlark_type .BOOLEAN {
266
+ // Form does not submit unchecked checkboxes, set to false
267
+ args [param .Name ] = starlark .Bool (false )
268
+ qsParams .Add (param .Name , "false" )
269
+ }
270
+ } else {
271
+ newVal , err := apptype .ParamStringToType (param .Name , param .Type , formValue )
272
+ if err != nil {
273
+ http .Error (w , err .Error (), http .StatusBadRequest )
274
+ return
275
+ }
276
+ args [param .Name ] = newVal
277
+ qsParams .Add (param .Name , formValue )
278
+ }
230
279
}
231
280
}
232
281
@@ -540,11 +589,13 @@ func RunDeferredCleanup(thread *starlark.Thread) error {
540
589
}
541
590
542
591
type ParamDef struct {
543
- Name string
544
- Description string
545
- Value any
546
- InputType string
547
- Options []string
592
+ Name string
593
+ Description string
594
+ Value any
595
+ InputType string
596
+ Options []string
597
+ DisplayType string
598
+ DisplayTypeOptions string
548
599
}
549
600
550
601
const (
@@ -570,6 +621,7 @@ func (a *Action) getForm(w http.ResponseWriter, r *http.Request) {
570
621
}
571
622
}
572
623
624
+ hasFileUpload := false
573
625
for _ , p := range a .params {
574
626
if strings .HasPrefix (p .Name , OPTIONS_PREFIX ) || a .hidden [p .Name ] {
575
627
continue
@@ -610,6 +662,24 @@ func (a *Action) getForm(w http.ResponseWriter, r *http.Request) {
610
662
param .Value = value
611
663
}
612
664
665
+ if p .DisplayType != "" {
666
+ switch p .DisplayType {
667
+ case apptype .DisplayTypePassword :
668
+ param .DisplayType = "password"
669
+ case apptype .DisplayTypeTextArea :
670
+ param .DisplayType = "textarea"
671
+ case apptype .DisplayTypeFileUpload :
672
+ param .DisplayType = "file"
673
+ hasFileUpload = true
674
+ default :
675
+ http .Error (w , fmt .Sprintf ("invalid display type for %s: %s" , p .Name , p .DisplayType ), http .StatusInternalServerError )
676
+ return
677
+ }
678
+ param .DisplayTypeOptions = p .DisplayTypeOptions
679
+ } else {
680
+ param .DisplayType = "text"
681
+ }
682
+
613
683
params = append (params , param )
614
684
}
615
685
@@ -624,16 +694,17 @@ func (a *Action) getForm(w http.ResponseWriter, r *http.Request) {
624
694
}
625
695
626
696
input := map [string ]any {
627
- "dev" : a .isDev ,
628
- "name" : a .name ,
629
- "description" : a .description ,
630
- "appPath" : a .appPath ,
631
- "pagePath" : a .pagePath ,
632
- "params" : params ,
633
- "styleType" : string (a .StyleType ),
634
- "lightTheme" : a .LightTheme ,
635
- "darkTheme" : a .DarkTheme ,
636
- "links" : linksWithQS ,
697
+ "dev" : a .isDev ,
698
+ "name" : a .name ,
699
+ "description" : a .description ,
700
+ "appPath" : a .appPath ,
701
+ "pagePath" : a .pagePath ,
702
+ "params" : params ,
703
+ "styleType" : string (a .StyleType ),
704
+ "lightTheme" : a .LightTheme ,
705
+ "darkTheme" : a .DarkTheme ,
706
+ "links" : linksWithQS ,
707
+ "hasFileUpload" : hasFileUpload ,
637
708
}
638
709
err := a .actionTemplate .ExecuteTemplate (w , "form.go.html" , input )
639
710
if err != nil {
0 commit comments