1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-28 23:37:04 +00:00

Update the _examples/mvc/login example - add a Path property at mvc.Response and add a context.Proceed helper function

Former-commit-id: b898901fe4a324e888a6e09c93530cf7a551cf2a
This commit is contained in:
Gerasimos (Makis) Maropoulos
2017-10-12 16:28:41 +03:00
parent b0f8329768
commit 32d14db46d
33 changed files with 1073 additions and 50 deletions

View File

@@ -0,0 +1,189 @@
// file: controllers/user_controller.go
package controllers
import (
"github.com/kataras/iris/_examples/mvc/login/datamodels"
"github.com/kataras/iris/_examples/mvc/login/services"
"github.com/kataras/iris/context"
"github.com/kataras/iris/mvc"
"github.com/kataras/iris/sessions"
)
// UserController is our /user controller.
// UserController is responsible to handle the following requests:
// GET /user/register
// POST /user/register
// GET /user/login
// POST /user/login
// GET /user/me
// All HTTP Methods /user/logout
type UserController struct {
// mvc.C is just a lightweight lightweight alternative
// to the "mvc.Controller" controller type,
// use it when you don't need mvc.Controller's fields
// (you don't need those fields when you return values from the method functions).
mvc.C
// Our UserService, it's an interface which
// is binded from the main application.
Service services.UserService
// Session-relative things.
Manager *sessions.Sessions
Session *sessions.Session
}
// BeginRequest will set the current session to the controller.
//
// Remember: iris.Context and context.Context is exactly the same thing,
// iris.Context is just a type alias for go 1.9 users.
// We use context.Context here because we don't need all iris' root functions,
// when we see the import paths, we make it visible to ourselves that this file is using only the context.
func (c *UserController) BeginRequest(ctx context.Context) {
c.C.BeginRequest(ctx)
if c.Manager == nil {
ctx.Application().Logger().Errorf(`UserController: sessions manager is nil, you should bind it`)
ctx.StopExecution() // dont run the main method handler and any "done" handlers.
return
}
c.Session = c.Manager.Start(ctx)
}
const userIDKey = "UserID"
func (c *UserController) getCurrentUserID() int64 {
userID, _ := c.Session.GetInt64Default(userIDKey, 0)
return userID
}
func (c *UserController) isLoggedIn() bool {
return c.getCurrentUserID() > 0
}
func (c *UserController) logout() {
c.Manager.DestroyByID(c.Session.ID())
}
var registerStaticView = mvc.View{
Name: "user/register.html",
Data: context.Map{"Title": "User Registration"},
}
// GetRegister handles GET: http://localhost:8080/user/register.
func (c *UserController) GetRegister() mvc.Result {
if c.isLoggedIn() {
c.logout()
}
return registerStaticView
}
// PostRegister handles POST: http://localhost:8080/user/register.
func (c *UserController) PostRegister() mvc.Result {
// get firstname, username and password from the form.
var (
firstname = c.Ctx.FormValue("firstname")
username = c.Ctx.FormValue("username")
password = c.Ctx.FormValue("password")
)
// create the new user, the password will be hashed by the service.
u, err := c.Service.Create(password, datamodels.User{
Username: username,
Firstname: firstname,
})
// set the user's id to this session even if err != nil,
// the zero id doesn't matters because .getCurrentUserID() checks for that.
// If err != nil then it will be shown, see below on mvc.Response.Err: err.
c.Session.Set(userIDKey, u.ID)
return mvc.Response{
// if not nil then this error will be shown instead.
Err: err,
// redirect to /user/me.
Path: "/user/me",
// When redirecting from POST to GET request you -should- use this HTTP status code,
// however there're some (complicated) alternatives if you
// search online or even the HTTP RFC.
// Status "See Other" RFC 7231, however iris can automatically fix that
// but it's good to know you can set a custom code;
// Code: 303,
}
}
var loginStaticView = mvc.View{
Name: "user/login.html",
Data: context.Map{"Title": "User Login"},
}
// GetLogin handles GET: http://localhost:8080/user/login.
func (c *UserController) GetLogin() mvc.Result {
if c.isLoggedIn() {
// if it's already logged in then destroy the previous session.
c.logout()
}
return loginStaticView
}
// PostLogin handles POST: http://localhost:8080/user/register.
func (c *UserController) PostLogin() mvc.Result {
var (
username = c.Ctx.FormValue("username")
password = c.Ctx.FormValue("password")
)
u, found := c.Service.GetByUsernameAndPassword(username, password)
if !found {
return mvc.Response{
Path: "/user/register",
}
}
c.Session.Set(userIDKey, u.ID)
return mvc.Response{
Path: "/user/me",
}
}
// GetMe handles GET: http://localhost:8080/user/me.
func (c *UserController) GetMe() mvc.Result {
if !c.isLoggedIn() {
// if it's not logged in then redirect user to the login page.
return mvc.Response{Path: "/user/login"}
}
u, found := c.Service.GetByID(c.getCurrentUserID())
if !found {
// if the session exists but for some reason the user doesn't exist in the "database"
// then logout and re-execute the function, it will redirect the client to the
// /user/login page.
c.logout()
return c.GetMe()
}
return mvc.View{
Name: "user/me.html",
Data: context.Map{
"Title": "Profile of " + u.Username,
"User": u,
},
}
}
// AnyLogout handles All/Any HTTP Methods for: http://localhost:8080/user/logout.
func (c *UserController) AnyLogout() {
if c.isLoggedIn() {
c.logout()
}
c.Ctx.Redirect("/user/login")
}

View File

@@ -0,0 +1,101 @@
package controllers
import (
"github.com/kataras/iris/_examples/mvc/login/datamodels"
"github.com/kataras/iris/_examples/mvc/login/services"
"github.com/kataras/iris/mvc"
)
// UsersController is our /users API controller.
// GET /users | get all
// GET /users/{id:long} | get by id
// PUT /users/{id:long} | update by id
// DELETE /users/{id:long} | delete by id
// Requires basic authentication.
type UsersController struct {
mvc.C
Service services.UserService
}
// This could be possible but we should not call handlers inside the `BeginRequest`.
// Because `BeginRequest` was introduced to set common, shared variables between all method handlers
// before their execution.
// We will add this middleware from our `app.Controller` call.
//
// var authMiddleware = basicauth.New(basicauth.Config{
// Users: map[string]string{
// "admin": "password",
// },
// })
//
// func (c *UsersController) BeginRequest(ctx iris.Context) {
// c.C.BeginRequest(ctx)
//
// if !ctx.Proceed(authMiddleware) {
// ctx.StopExecution()
// }
// }
// Get returns list of the users.
// Demo:
// curl -i -u admin:password http://localhost:8080/users
//
// The correct way if you have sensitive data:
// func (c *UsersController) Get() (results []viewmodels.User) {
// data := c.Service.GetAll()
//
// for _, user := range data {
// results = append(results, viewmodels.User{user})
// }
// return
// }
// otherwise just return the datamodels.
func (c *UsersController) Get() (results []datamodels.User) {
return c.Service.GetAll()
}
// GetBy returns a user.
// Demo:
// curl -i -u admin:password http://localhost:8080/users/1
func (c *UsersController) GetBy(id int64) (user datamodels.User, found bool) {
u, found := c.Service.GetByID(id)
if !found {
// this message will be binded to the
// main.go -> app.OnAnyErrorCode -> NotFound -> shared/error.html -> .Message text.
c.Ctx.Values().Set("message", "User couldn't be found!")
}
return u, found // it will throw/emit 404 if found == false.
}
// PutBy updates a user.
// Demo:
// curl -i -X PUT -u admin:password -F "username=kataras"
// -F "password=rawPasswordIsNotSafeIfOrNotHTTPs_You_Should_Use_A_client_side_lib_for_hash_as_well"
// http://localhost:8080/users/1
func (c *UsersController) PutBy(id int64) (datamodels.User, error) {
// username := c.Ctx.FormValue("username")
// password := c.Ctx.FormValue("password")
u := datamodels.User{}
if err := c.Ctx.ReadForm(&u); err != nil {
return u, err
}
return c.Service.Update(id, u)
}
// DeleteBy deletes a user.
// Demo:
// curl -i -X DELETE -u admin:password http://localhost:8080/users/1
func (c *UsersController) DeleteBy(id int64) interface{} {
wasDel := c.Service.DeleteByID(id)
if wasDel {
// return the deleted user's ID
return map[string]interface{}{"deleted": id}
}
// right here we can see that a method function
// can return any of those two types(map or int),
// we don't have to specify the return type to a specific type.
return 400 // same as `iris.StatusBadRequest`.
}

View File

@@ -0,0 +1,12 @@
// file: middleware/basicauth.go
package middleware
import "github.com/kataras/iris/middleware/basicauth"
// BasicAuth middleware sample.
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
})

View File

@@ -0,0 +1,61 @@
/* Bordered form */
form {
border: 3px solid #f1f1f1;
}
/* Full-width inputs */
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}
/* Set a style for all buttons */
button {
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
cursor: pointer;
width: 100%;
}
/* Add a hover effect for buttons */
button:hover {
opacity: 0.8;
}
/* Extra style for the cancel button (red) */
.cancelbtn {
width: auto;
padding: 10px 18px;
background-color: #f44336;
}
/* Center the container */
/* Add padding to containers */
.container {
padding: 16px;
}
/* The "Forgot password" text */
span.psw {
float: right;
padding-top: 16px;
}
/* Change styles for span and cancel button on extra small screens */
@media screen and (max-width: 300px) {
span.psw {
display: block;
float: none;
}
.cancelbtn {
width: 100%;
}
}

View File

@@ -0,0 +1,55 @@
# View Models
There should be the view models, the structure that the client will be able to see.
Example:
```go
import (
"github.com/kataras/iris/_examples/mvc/login/datamodels"
"github.com/kataras/iris/context"
)
type User struct {
datamodels.User
}
func (m User) IsValid() bool {
/* do some checks and return true if it's valid... */
return m.ID > 0
}
```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoritically, something like the following is permitted if it's really necessary;
```go
// Dispatch completes the `kataras/iris/mvc#Result` interface.
// Sends a `User` as a controlled http response.
// If its ID is zero or less then it returns a 404 not found error
// else it returns its json representation,
// (just like the controller's functions do for custom types by default).
//
// Don't overdo it, the application's logic should not be here.
// It's just one more step of validation before the response,
// simple checks can be added here.
//
// It's just a showcase,
// imagine the potentials this feature gives when designing a bigger application.
//
// This is called where the return value from a controller's method functions
// is type of `User`.
// For example the `controllers/user_controller.go#GetBy`.
func (m User) Dispatch(ctx context.Context) {
if !m.IsValid() {
ctx.NotFound()
return
}
ctx.JSON(m, context.JSON{Indent: " "})
}
```
However, we will use the "datamodels" as the only one models package because
User structure doesn't contain any sensitive data, clients are able to see all of its fields
and we don't need any extra functionality or validation inside it.

View File

@@ -0,0 +1,15 @@
<h1>Error.</h1>
<h2>An error occurred while processing your request.</h2>
<h3>{{.Message}}</h3>
<footer>
<h2>Sitemap</h2>
<a href="http://localhost:8080/user/register">/user/register</a><br/>
<a href="http://localhost:8080/user/login">/user/login</a><br/>
<a href="http://localhost:8080/user/logout">/user/logout</a><br/>
<a href="http://localhost:8080/user/me">/user/me</a><br/>
<h3>requires authentication</h3><br/>
<a href="http://localhost:8080/users">/users</a><br/>
<a href="http://localhost:8080/users/1">/users/{id}</a><br/>
</footer>

View File

@@ -0,0 +1,12 @@
<html>
<head>
<title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/public/css/site.css" />
</head>
<body>
{{ yield }}
</body>
</html>

View File

@@ -0,0 +1,11 @@
<form action="/user/login" method="POST">
<div class="container">
<label><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>
<label><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Login</button>
</div>
</form>

View File

@@ -0,0 +1,3 @@
<p>
Welcome back <strong>{{.User.Firstname}}</strong>!
</p>

View File

@@ -0,0 +1,14 @@
<form action="/user/register" method="POST">
<div class="container">
<label><b>Firstname</b></label>
<input type="text" placeholder="Enter Firstname" name="firstname" required>
<label><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>
<label><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Register</button>
</div>
</form>