Add RunTransactions() approach to application design
This commit is contained in:
1 parent
7b5603eb11
commit
78aaf72e84
9 files changed
+804
-24
No files matched your search
@@ -0,0 +1,257 @@
|
||||
// This file is part of https://github.com/racingmars/go3270/
|
||||
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
||||
// LICENSE in the project root for license information.
|
||||
|
||||
// This example demonstrates using the RunTransactions() approach to
|
||||
// structuring a go3270 application.
|
||||
|
||||
// This file contains the login and user registration transactions.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/racingmars/go3270"
|
||||
)
|
||||
|
||||
const loginUsername = "username"
|
||||
const loginPassword = "password"
|
||||
const loginPasswordConf = "password confirmation"
|
||||
const loginName = "name"
|
||||
const loginErr = "errormsg"
|
||||
|
||||
var loginScreen = go3270.Screen{
|
||||
{Row: 0, Col: 37, Intense: true, Content: "Logon"},
|
||||
{Row: 2, Col: 0,
|
||||
Content: "Welcome to the go3270 example application. Please log on."},
|
||||
|
||||
// Username
|
||||
{Row: 4, Col: 0, Content: "Username . . .", Color: go3270.Green},
|
||||
{Row: 4, Col: 15, Name: loginUsername, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 4, Col: 24, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Password
|
||||
{Row: 5, Col: 0, Content: "Password . . .", Color: go3270.Green},
|
||||
{Row: 5, Col: 15, Name: loginPassword, Write: true, Hidden: true},
|
||||
{Row: 5, Col: 79}, // field "stop" character
|
||||
|
||||
// Registration instructions
|
||||
{Row: 7, Col: 0, Content: "If you don't yet have an account, press"},
|
||||
{Row: 7, Col: 40, Content: "PF5", Color: go3270.White, Intense: true},
|
||||
{Row: 7, Col: 44, Content: "to register a new account."},
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: loginErr, Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F1=Help"},
|
||||
{Row: 23, Col: 14, Content: "F3=Exit"},
|
||||
{Row: 23, Col: 27, Content: "F5=Register"},
|
||||
}
|
||||
|
||||
var loginScreenRules = go3270.Rules{
|
||||
loginUsername: {Validator: go3270.NonBlank},
|
||||
loginPassword: {Validator: go3270.NonBlank, Reset: true},
|
||||
}
|
||||
|
||||
// login transaction accepts a string value in data if the login screen
|
||||
// should be initialized with an error message.
|
||||
func (sess *session) login(conn net.Conn, data any) (go3270.Tx, any, error) {
|
||||
|
||||
fieldValues := make(map[string]string)
|
||||
|
||||
if data != nil {
|
||||
if errmsg, ok := data.(string); ok {
|
||||
fieldValues[loginErr] = errmsg
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := go3270.HandleScreen(
|
||||
loginScreen, // the screen to display
|
||||
loginScreenRules, // the rules to enforce
|
||||
fieldValues, // pre-populated values in fields
|
||||
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
||||
[]go3270.AID{ // keys we accept -- non-validating
|
||||
go3270.AIDPF1,
|
||||
go3270.AIDPF3,
|
||||
go3270.AIDPF5,
|
||||
go3270.AIDClear},
|
||||
loginErr, // name of field to put error messages in
|
||||
4, 16, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch resp.AID {
|
||||
case go3270.AIDClear:
|
||||
// re-run the transaction with empty values
|
||||
return sess.login, nil, nil
|
||||
case go3270.AIDPF1:
|
||||
// Display help transaction, then return to this transaction
|
||||
return help(sess.login), nil, nil
|
||||
case go3270.AIDPF3:
|
||||
// User wants to quit; return no next transaction
|
||||
return nil, nil, nil
|
||||
case go3270.AIDPF5:
|
||||
// Send user to registration transaction
|
||||
return sess.newuser, nil, nil
|
||||
}
|
||||
|
||||
// If we didn't get one of the other allowed keys, try to log the user in.
|
||||
username := resp.Values[loginUsername]
|
||||
password := resp.Values[loginPassword]
|
||||
|
||||
user, err := sess.db.GetUser(username)
|
||||
// NOTE: this is just an example application. Obviously in a real
|
||||
// application, password hashing would be in place.
|
||||
if err != nil || user.Password != password {
|
||||
// If we couldn't get the username from the DB, or if the password
|
||||
// doesn't match, re-run the login transaction with an error
|
||||
// message displayed.
|
||||
|
||||
return sess.login, "Username or password not valid.", nil
|
||||
}
|
||||
|
||||
// Login was successful. Update the session state to the logged-in user.
|
||||
sess.user = user
|
||||
|
||||
return sess.mainmenu, nil, nil
|
||||
}
|
||||
|
||||
var newuserScreen = go3270.Screen{
|
||||
{Row: 0, Col: 30, Intense: true, Content: "New User Registration"},
|
||||
{Row: 2, Col: 0,
|
||||
Content: "Please provide your user registration details."},
|
||||
|
||||
// Username
|
||||
{Row: 4, Col: 0, Content: "Username . . .", Color: go3270.Green},
|
||||
{Row: 4, Col: 15, Name: loginUsername, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 4, Col: 24, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Password
|
||||
{Row: 5, Col: 0, Content: "Password . . .", Color: go3270.Green},
|
||||
{Row: 5, Col: 15, Name: loginPassword, Write: true, Hidden: true},
|
||||
{Row: 5, Col: 79, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Password
|
||||
{Row: 6, Col: 0, Content: "Confirm Pass .", Color: go3270.Green},
|
||||
{Row: 6, Col: 15, Name: loginPasswordConf, Write: true, Hidden: true},
|
||||
{Row: 6, Col: 79, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Name
|
||||
{Row: 7, Col: 0, Content: "Name . . . . .", Color: go3270.Green},
|
||||
{Row: 7, Col: 15, Name: loginName, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 7, Col: 46, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: loginErr, Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F1=Help"},
|
||||
{Row: 23, Col: 14, Content: "F3=Exit"},
|
||||
}
|
||||
|
||||
var newuserScreenRules = go3270.Rules{
|
||||
loginUsername: {Validator: go3270.NonBlank},
|
||||
loginPassword: {Validator: go3270.NonBlank, Reset: true},
|
||||
loginPasswordConf: {Validator: go3270.NonBlank, Reset: true},
|
||||
}
|
||||
|
||||
// newuserData is the data that can be passed into the newuser transaction
|
||||
type newuserData struct {
|
||||
username string
|
||||
name string
|
||||
errmsg string
|
||||
}
|
||||
|
||||
func (sess *session) newuser(conn net.Conn, data any) (go3270.Tx, any, error) {
|
||||
|
||||
fieldValues := make(map[string]string)
|
||||
|
||||
if data != nil {
|
||||
if newdata, ok := data.(newuserData); ok {
|
||||
fieldValues[loginUsername] = newdata.username
|
||||
fieldValues[loginName] = newdata.name
|
||||
fieldValues[loginErr] = newdata.errmsg
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := go3270.HandleScreen(
|
||||
newuserScreen, // the screen to display
|
||||
newuserScreenRules, // the rules to enforce
|
||||
fieldValues, // pre-populated values in fields
|
||||
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
||||
[]go3270.AID{ // keys we accept -- non-validating
|
||||
go3270.AIDPF1,
|
||||
go3270.AIDPF3,
|
||||
go3270.AIDClear,
|
||||
},
|
||||
loginErr, // name of field to put error messages in
|
||||
4, 16, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch resp.AID {
|
||||
case go3270.AIDClear:
|
||||
// Re-run transaction with empty values
|
||||
return sess.newuser, nil, nil
|
||||
case go3270.AIDPF1:
|
||||
// Display help screen, returning to this transaction after
|
||||
return help(sess.newuser), nil, nil
|
||||
case go3270.AIDPF3:
|
||||
// User wants to cancel registration, go back to login screen.
|
||||
return sess.login, nil, nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll try creating the user.
|
||||
|
||||
username := resp.Values[loginUsername]
|
||||
password := resp.Values[loginPassword]
|
||||
passwordConf := resp.Values[loginPasswordConf]
|
||||
name := resp.Values[loginName]
|
||||
|
||||
if password != passwordConf {
|
||||
// Re-run the newuser transaction with an error message
|
||||
return sess.newuser, newuserData{
|
||||
username: username,
|
||||
name: name,
|
||||
errmsg: "Passwords do not match.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
user, err := sess.db.CreateUser(User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Name: name,
|
||||
})
|
||||
|
||||
if err == ErrUserExists {
|
||||
// Re-run the newuser transaction with an error message
|
||||
return sess.newuser, newuserData{
|
||||
username: username,
|
||||
name: name,
|
||||
errmsg: "Username already exists; please choose a new one.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return sess.newuser, newuserData{
|
||||
username: username,
|
||||
name: name,
|
||||
errmsg: "Unknown error creating new user.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Success! We'll stick the new user in the session state like login does
|
||||
// and go to the main menu.
|
||||
sess.user = user
|
||||
|
||||
return sess.mainmenu, nil, nil
|
||||
}
|
||||
Reference in new issue
Block a user