Commit c0517ee
Eric Bower
·
2026-03-28 23:12:36 -0400 EDT
parent c0208b3
docs: rm auto form
5 files changed,
+6,
-135
+0,
-124
| ... | ... | @@ -1,124 +0,0 @@ | |
| 1 | - | # Auto-Form Feature for pgs.sh | |
| 2 | - | ||
| 3 | - | ## Overview | |
| 4 | - | ||
| 5 | - | The auto-form feature allows users to create HTML forms on their pgs.sh sites that automatically submit data to the pgs database. Form submissions can then be retrieved via the pgs CLI in JSON format. | |
| 6 | - | ||
| 7 | - | ## How It Works | |
| 8 | - | ||
| 9 | - | 1. Form submissions are automatically captured and stored in PostgreSQL | |
| 10 | - | 2. Users can retrieve form data via the pgs CLI | |
| 11 | - | ||
| 12 | - | ## Database Schema | |
| 13 | - | ||
| 14 | - | ```sql | |
| 15 | - | CREATE TABLE form_entries ( | |
| 16 | - | id uuid NOT NULL DEFAULT uuid_generate_v4(), | |
| 17 | - | user_id uuid NOT NULL, | |
| 18 | - | name VARCHAR(255) NOT NULL, | |
| 19 | - | data jsonb NOT NULL, | |
| 20 | - | created_at timestamp without time zone NOT NULL DEFAULT NOW(), | |
| 21 | - | CONSTRAINT form_entries_pkey PRIMARY KEY (id), | |
| 22 | - | CONSTRAINT fk_form_entries_users | |
| 23 | - | FOREIGN KEY(user_id) | |
| 24 | - | REFERENCES app_users(id) | |
| 25 | - | ON DELETE CASCADE | |
| 26 | - | ); | |
| 27 | - | ||
| 28 | - | CREATE INDEX IF NOT EXISTS idx_form_entries_user ON form_entries(user_id); | |
| 29 | - | CREATE INDEX IF NOT EXISTS idx_form_entries_name ON form_entries(name); | |
| 30 | - | ``` | |
| 31 | - | ||
| 32 | - | ### Migration | |
| 33 | - | ||
| 34 | - | Run the migration: | |
| 35 | - | ```bash | |
| 36 | - | make latest | |
| 37 | - | ``` | |
| 38 | - | ||
| 39 | - | Or run all migrations: | |
| 40 | - | ```bash | |
| 41 | - | make migrate | |
| 42 | - | ``` | |
| 43 | - | ||
| 44 | - | ## HTML Form Example | |
| 45 | - | ||
| 46 | - | ```html | |
| 47 | - | <form method="POST" action="/forms/example" data-pgs="true"> | |
| 48 | - | <p> | |
| 49 | - | <label>Your Name: <input type="text" name="name" /></label> | |
| 50 | - | </p> | |
| 51 | - | <p> | |
| 52 | - | <label>Your Email: <input type="email" name="email" /></label> | |
| 53 | - | </p> | |
| 54 | - | <p> | |
| 55 | - | <label>Your Role: <select name="role[]" multiple> | |
| 56 | - | <option value="leader">Leader</option> | |
| 57 | - | <option value="follower">Follower</option> | |
| 58 | - | </select></label> | |
| 59 | - | </p> | |
| 60 | - | <p> | |
| 61 | - | <label>Message: <textarea name="message"></textarea></label> | |
| 62 | - | </p> | |
| 63 | - | <p> | |
| 64 | - | <button type="submit">Send</button> | |
| 65 | - | </p> | |
| 66 | - | </form> | |
| 67 | - | ``` | |
| 68 | - | ||
| 69 | - | ## CLI Commands | |
| 70 | - | ||
| 71 | - | ### List all form names for a user | |
| 72 | - | ```bash | |
| 73 | - | ssh pgs.sh forms ls | |
| 74 | - | ``` | |
| 75 | - | ||
| 76 | - | ### Get form submissions for a specific form | |
| 77 | - | ```bash | |
| 78 | - | ssh pgs.sh forms show example | |
| 79 | - | ``` | |
| 80 | - | ||
| 81 | - | Output (JSON): | |
| 82 | - | ```json | |
| 83 | - | [ | |
| 84 | - | { | |
| 85 | - | "id": "uuid", | |
| 86 | - | "name": "contact", | |
| 87 | - | "data": { | |
| 88 | - | "name": "John Doe", | |
| 89 | - | "email": "john@example.com", | |
| 90 | - | "role": ["leader", "follower"], | |
| 91 | - | "message": "Hello!" | |
| 92 | - | }, | |
| 93 | - | "created_at": "2026-03-05T12:00:00Z" | |
| 94 | - | } | |
| 95 | - | ] | |
| 96 | - | ``` | |
| 97 | - | ||
| 98 | - | ### Delete all submissions for a form | |
| 99 | - | ```bash | |
| 100 | - | ssh pgs.sh forms rm example --write | |
| 101 | - | ``` | |
| 102 | - | ||
| 103 | - | ## Data Storage | |
| 104 | - | ||
| 105 | - | - Form data is stored in PostgreSQL `form_entries` table | |
| 106 | - | - Each submission is a JSON object with form field names as keys | |
| 107 | - | - Data is associated with the user, not the project | |
| 108 | - | - Form data is deleted when the user account is deleted (CASCADE) | |
| 109 | - | ||
| 110 | - | ## Implementation Details | |
| 111 | - | ||
| 112 | - | ### Database Methods | |
| 113 | - | ||
| 114 | - | ```go | |
| 115 | - | InsertFormEntry(userID, name string, data map[string]interface{}) error | |
| 116 | - | FindFormEntriesByUserAndName(userID, name string) ([]*db.FormEntry, error) | |
| 117 | - | FindFormNamesByUser(userID string) ([]string, error) | |
| 118 | - | RemoveFormEntriesByUserAndName(userID, name string) error | |
| 119 | - | ``` | |
| 120 | - | ||
| 121 | - | ## More features | |
| 122 | - | ||
| 123 | - | - Form validation and confirmation pages | |
| 124 | - | - CSRF token in form and validated in post handler |
+1,
-3
| ... | ... | @@ -144,9 +144,7 @@ func (h *UploadAssetHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils | |
| 144 | 144 | fileInfo.FSize = info.Size | |
| 145 | 145 | fileInfo.FModTime = info.LastModified | |
| 146 | 146 | ||
| 147 | - | reader := storage.NewAllReaderAt(contents) | |
| 148 | - | ||
| 149 | - | return fileInfo, reader, nil | |
| 147 | + | return fileInfo, contents, nil | |
| 150 | 148 | } | |
| 151 | 149 | ||
| 152 | 150 | func (h *UploadAssetHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) { |
+1,
-2
| ... | ... | @@ -123,7 +123,6 @@ func (h *UploadImgHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils.F | |
| 123 | 123 | if err != nil { | |
| 124 | 124 | return nil, nil, err | |
| 125 | 125 | } | |
| 126 | - | reader := storage.NewAllReaderAt(contents) | |
| 127 | 126 | ||
| 128 | 127 | fileInfo := &sendutils.VirtualFile{ | |
| 129 | 128 | FName: cleanFilename, |
| ... | ... | @@ -132,7 +131,7 @@ func (h *UploadImgHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils.F | |
| 132 | 131 | FModTime: info.LastModified, | |
| 133 | 132 | } | |
| 134 | 133 | ||
| 135 | - | return fileInfo, reader, nil | |
| 134 | + | return fileInfo, contents, nil | |
| 136 | 135 | } | |
| 137 | 136 | ||
| 138 | 137 | func (h *UploadImgHandler) Write(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (string, error) { |
+3,
-3
| ... | ... | @@ -10,9 +10,8 @@ type ReadAndReaderAt interface { | |
| 10 | 10 | } | |
| 11 | 11 | ||
| 12 | 12 | type ReadAndReaderAtCloser interface { | |
| 13 | - | io.Reader | |
| 14 | 13 | io.ReaderAt | |
| 15 | - | io.ReadCloser | |
| 14 | + | io.ReadSeekCloser | |
| 16 | 15 | } | |
| 17 | 16 | ||
| 18 | 17 | func NopReadAndReaderAtCloser(r ReadAndReaderAt) ReadAndReaderAtCloser { |
| ... | ... | @@ -23,4 +22,5 @@ type nopReadAndReaderAt struct { | |
| 23 | 22 | ReadAndReaderAt | |
| 24 | 23 | } | |
| 25 | 24 | ||
| 26 | - | func (nopReadAndReaderAt) Close() error { return nil } | |
| 25 | + | func (nopReadAndReaderAt) Close() error { return nil } | |
| 26 | + | func (nopReadAndReaderAt) Seek(int64, int) (int64, error) { return 0, nil } |
+1,
-3
| ... | ... | @@ -109,9 +109,7 @@ func (h *UploadAssetHandler) Read(s *pssh.SSHServerConnSession, entry *utils.Fil | |
| 109 | 109 | fileInfo.FSize = info.Size | |
| 110 | 110 | fileInfo.FModTime = info.LastModified | |
| 111 | 111 | ||
| 112 | - | reader := NewAllReaderAt(contents) | |
| 113 | - | ||
| 114 | - | return fileInfo, reader, nil | |
| 112 | + | return fileInfo, contents, nil | |
| 115 | 113 | } | |
| 116 | 114 | ||
| 117 | 115 | func (h *UploadAssetHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) { |