Skip to content

Commit

Permalink
Implements blog app
Browse files Browse the repository at this point in the history
Use policy as Authorization framework
  • Loading branch information
rainboyan committed May 31, 2024
1 parent 1d5c00d commit 4a209d1
Show file tree
Hide file tree
Showing 27 changed files with 565 additions and 3 deletions.
Binary file added app/assets/images/apple-touch-icon-retina.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/favicon.ico
Binary file not shown.
22 changes: 22 additions & 0 deletions app/assets/images/grace.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This is a manifest file that'll be compiled into application.js.
//
// Any JavaScript file within this directory can be referenced here using a relative path.
//
// You're free to add application-wide JavaScript to this file, but it's generally better
// to create separate JavaScript files as needed.
//
//= require_self
11 changes: 11 additions & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS file within this directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*/
3 changes: 3 additions & 0 deletions app/conf/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<appender-ref ref="STDOUT" />
</root>
<logger name="org.hibernate.orm.deprecation" level="error" />
<logger name="org.hibernate.SQL" level="debug" />
<logger name="org.hibernate.type.descriptor.sql" level="trace" />
<logger name="grails.core.DefaultGrailsApplication" level="debug" />
<logger name="org.grails.plugins.support" level="debug" />
<logger name="grace.demos.Application" level="debug" />
</configuration>
34 changes: 34 additions & 0 deletions app/controllers/grace/demos/AuthController.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package grace.demos

class AuthController {

static allowedMethods = [index: "GET", login: "POST", logout: "GET"]

def index() {
if (session["current_user"]) {
redirect(uri: '/')
}
}

def login(LoginCommand cmd) {
User user = User.findByUsernameAndPassword(cmd.username, cmd.password)
if (user) {
session["current_user"] = user
redirect(uri: '/')
} else {
redirect(controller: 'auth', action: 'index', params: [fail: true])
}
}

def logout() {
log.info "User logout"
session.invalidate()
redirect(action: "index")
}

}

class LoginCommand {
String username
String password
}
55 changes: 55 additions & 0 deletions app/controllers/grace/demos/PostController.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package grace.demos

import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*

class PostController {

def postService

def index() {
authorize()
respond postService.findAll()
}

def show(Long id) {
def post = postService.get(id)
if (post == null) {
flash.message = "Post#${id} not found"
redirect action: "index", method: "GET"
return
}
authorize(post)
respond post
}

def edit(Long id) {
def post = postService.get(id)
if (post == null) {
flash.message = "Post#${id} not found"
redirect action: "index", method: "GET"
return
}
authorize(post)
respond post
}

def update(Long id) {
def post = postService.get(id)
if (post == null) {
flash.message = "Post#${id} not found"
redirect action: "index", method: "GET"
return
}

authorize(post)

post.title = params.title
post.content = params.content
postService.save(post)

flash.message = 'Post updated'
redirect post
}

}
7 changes: 4 additions & 3 deletions app/controllers/grace/demos/UrlMappings.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ class UrlMappings {
}
}

"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
"/"(view: "/index")
"500"(view: '/error')
"404"(view: '/notFound')
"403"(view: '/403')
}
}
17 changes: 17 additions & 0 deletions app/domain/grace/demos/Post.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package grace.demos

class Post {

String title
String content
String status
User author

static constraints = {
title(blank: false, nullable: false, maxSize: 255)
content(blank: true, nullable: true, maxSize: 255)
status(blank: false, nullable: false, maxSize: 10)
author(nullable: true)
}

}
19 changes: 19 additions & 0 deletions app/domain/grace/demos/Role.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package grace.demos

class Role {

String name

static constraints = {
name(blank: false, nullable: false, maxSize: 60)
}

static mapping = {
table "roles"
}

String toString() {
name
}

}
34 changes: 34 additions & 0 deletions app/domain/grace/demos/User.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package grace.demos

class User {

String username
String password

Set<Role> getRoles() {
(UserRole.findAllByUser(this) as List<UserRole>)*.role as Set<Role>
}

boolean hasRole(String name) {
Role role = Role.findByName(name)
role ? UserRole.findByUserAndRole(this, role) : false
}

boolean isAdmin() {
hasRole('ROLE_ADMIN')
}

static constraints = {
username(blank: false, nullable: false, maxSize: 60)
password(blank: false, nullable: false, maxSize: 30)
}

static mapping = {
table "users"
}

String toString() {
username
}

}
48 changes: 48 additions & 0 deletions app/domain/grace/demos/UserRole.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package grace.demos

import groovy.transform.ToString

import org.codehaus.groovy.util.HashCodeHelper
import grails.compiler.GrailsCompileStatic

@GrailsCompileStatic
@ToString(cache=true, includeNames=true, includePackage=false)
class UserRole implements Serializable {
private static final long serialVersionUID = 1

User user
Role role

@Override
boolean equals(other) {
if (other instanceof UserRole) {
other.userId == user?.id && other.roleId == role?.id
}
}

@Override
int hashCode() {
int hashCode = HashCodeHelper.initHash()
if (user) {
hashCode = HashCodeHelper.updateHash(hashCode, user.id)
}
if (role) {
hashCode = HashCodeHelper.updateHash(hashCode, role.id)
}
hashCode
}

static UserRole create(User user, Role role, boolean flush = false) {
def instance = new UserRole(user: user, role: role)
instance.save(flush: flush)
instance
}

static constraints = {
}

static mapping = {
id composite: ['user', 'role']
version false
}
}
Empty file added app/i18n/messages.properties
Empty file.
20 changes: 20 additions & 0 deletions app/init/grace/demos/BootStrap.groovy
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
package grace.demos

import grails.gorm.transactions.Transactional

class BootStrap {

def init = { servletContext ->
environments {
development {
loadSampleData()
}
}
}

def destroy = {
}

@Transactional
protected void loadSampleData() {
def ROLE_ADMIN = new Role(name: "ROLE_ADMIN").save()
def ROLE_USER = new Role(name: "ROLE_USER").save()
def admin = new User(username: "admin", password: "admin").save()
def user = new User(username: "user", password: "user").save()
UserRole.create(admin, ROLE_ADMIN)
UserRole.create(user, ROLE_USER)
new Post(title: "The first post", content: "This is a content", status: "DRAFT", author: user).save()
new Post(title: "The second post", content: "This is a post by admin", status: "DRAFT", author: admin).save()
new Post(title: "This is another post", content: "This is also a content", status: "PUBLISHED").save()
}

}
21 changes: 21 additions & 0 deletions app/policies/grace/demos/PostPolicy.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package grace.demos

class PostPolicy {

def index() {
true
}

def show() {
record.status == 'PUBLISHED' || record?.author?.id == user?.id || user?.isAdmin()
}

def edit() {
update()
}

def update() {
record?.author?.id == user?.id
}

}
21 changes: 21 additions & 0 deletions app/services/grace/demos/PostService.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package grace.demos

import grails.gorm.transactions.Transactional

class PostService {

List<Post> findAll() {
Post.list()
}

Post get(Long id) {
Post post = Post.get(id)
post
}

@Transactional
void save(Post post) {
post.save(flush: true)
}

}
15 changes: 15 additions & 0 deletions app/views/403.gsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<title>No Access</title>
<meta name="layout" content="main">
</head>
<body>
<div id="content" role="main">
<div class="container">
<h1>Error: 403</h1>
<p>Path: ${request.forwardURI}</p>
</div>
</div>
</body>
</html>
Loading

0 comments on commit 4a209d1

Please sign in to comment.