Skip to content

Commit 4b44018

Browse files
committed
GH-23: improved server app code base
1 parent a929f0d commit 4b44018

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1144
-554
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package org.ionproject.codegarten
22

3+
import org.ionproject.codegarten.pipeline.argumentresolvers.PaginationResolver
4+
import org.ionproject.codegarten.pipeline.argumentresolvers.UserResolver
35
import org.ionproject.codegarten.pipeline.interceptors.AuthorizationInterceptor
46
import org.jdbi.v3.core.Jdbi
57
import org.jdbi.v3.core.kotlin.KotlinPlugin
6-
import org.springframework.beans.factory.annotation.Autowired
78
import org.springframework.boot.autoconfigure.SpringBootApplication
89
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
910
import org.springframework.boot.runApplication
1011
import org.springframework.context.annotation.Bean
1112
import org.springframework.stereotype.Component
13+
import org.springframework.web.method.support.HandlerMethodArgumentResolver
1214
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
1315
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
1416

@@ -22,16 +24,19 @@ class CodeGartenApplication(private val configProperties: ConfigProperties) {
2224
}
2325

2426
@Component
25-
class MvcConfig : WebMvcConfigurer {
26-
27-
@Autowired
28-
private lateinit var authorizationInterceptor: AuthorizationInterceptor
27+
class MvcConfig(val authInterceptor: AuthorizationInterceptor) : WebMvcConfigurer {
2928

3029
override fun addInterceptors(registry: InterceptorRegistry) {
31-
registry.addInterceptor(authorizationInterceptor)
30+
registry.addInterceptor(authInterceptor)
31+
}
32+
33+
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
34+
resolvers.add(PaginationResolver())
35+
resolvers.add(UserResolver())
3236
}
3337
}
3438

3539
fun main(args: Array<String>) {
40+
System.setProperty("server.port", Routes.PORT)
3641
runApplication<CodeGartenApplication>(*args)
3742
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package org.ionproject.codegarten
2+
3+
import org.ionproject.codegarten.responses.siren.SirenLink
4+
import org.springframework.http.MediaType
5+
import org.springframework.web.util.UriTemplate
6+
import java.net.URI
7+
8+
object Routes {
9+
val INPUT_CONTENT_TYPE = MediaType.APPLICATION_FORM_URLENCODED
10+
11+
const val SCHEME = "http"
12+
const val PORT = "8080"
13+
const val HOST = "localhost:$PORT"
14+
const val API_BASE_URI = "/api"
15+
const val IM_BASE_URI = "/im"
16+
17+
const val PAGE_TEMPLATE_QUERY = "{?page,limit}"
18+
const val PAGE_QUERY = "?page={page}&limit={limit}"
19+
20+
const val ORG_PARAM = "orgId"
21+
const val CLASSROOM_PARAM = "classroomNumber"
22+
const val ASSIGNMENT_PARAM = "assignmentNumber"
23+
const val DELIVERY_PARAM = "deliveryNumber"
24+
const val USER_PARAM = "userId"
25+
26+
27+
// Error
28+
const val ERROR_HREF = "/error"
29+
30+
31+
// Interaction Manager routes
32+
const val AUTH_HREF = "$IM_BASE_URI/oauth"
33+
const val AUTH_CODE_HREF = "$AUTH_HREF/authorize"
34+
const val AUTH_TOKEN_HREF = "$AUTH_HREF/access_token"
35+
const val GH_INSTALLATIONS_HREF = "$IM_BASE_URI/github/install"
36+
const val GH_INSTALLATIONS_CB_HREF = "$IM_BASE_URI/github/install/cb"
37+
38+
39+
// Users
40+
const val USER_HREF = "$API_BASE_URI/user"
41+
const val USER_BY_ID_HREF = "$API_BASE_URI/users/{$USER_PARAM}"
42+
43+
val USER_BY_ID_HREF_TEMPLATE = UriTemplate(USER_BY_ID_HREF)
44+
45+
fun getUserByIdUri(userId: Int) = USER_BY_ID_HREF_TEMPLATE.expand(userId)
46+
47+
48+
// Organizations
49+
const val ORGS_HREF = "$API_BASE_URI/orgs"
50+
const val ORG_BY_ID_HREF = "$ORGS_HREF/{$ORG_PARAM}"
51+
52+
val ORG_BY_ID_HREF_TEMPLATE = UriTemplate(ORG_BY_ID_HREF)
53+
54+
fun getOrgByIdUri(orgId: Int) = ORG_BY_ID_HREF_TEMPLATE.expand(orgId)
55+
56+
57+
// Classrooms
58+
const val CLASSROOMS_HREF = "$ORG_BY_ID_HREF/classrooms"
59+
const val CLASSROOM_BY_NUMBER_HREF = "$CLASSROOMS_HREF/{$CLASSROOM_PARAM}"
60+
61+
val CLASSROOM_HREF_TEMPLATE = UriTemplate(CLASSROOMS_HREF)
62+
val CLASSROOM_BY_NUMBER_HREF_TEMPLATE = UriTemplate(CLASSROOM_BY_NUMBER_HREF)
63+
64+
fun getClassroomsUri(orgId: Int) = CLASSROOM_HREF_TEMPLATE.expand(orgId)
65+
fun getClassroomByNumberUri(orgId: Int, classroomNumber: Int) =
66+
CLASSROOM_BY_NUMBER_HREF_TEMPLATE.expand(orgId, classroomNumber)
67+
68+
69+
// Assignments
70+
const val ASSIGNMENTS_HREF = "$CLASSROOM_BY_NUMBER_HREF/assignments"
71+
const val ASSIGNMENT_BY_NUMBER_HREF = "$ASSIGNMENTS_HREF/{$ASSIGNMENT_PARAM}"
72+
const val ASSIGNMENTS_OF_USER_HREF = "$USER_HREF/assignments"
73+
74+
val ASSIGNMENTS_HREF_TEMPLATE = UriTemplate(ASSIGNMENTS_HREF)
75+
val ASSIGNMENT_BY_NUMBER_HREF_TEMPLATE = UriTemplate(ASSIGNMENT_BY_NUMBER_HREF)
76+
77+
fun getAssignmentsUri(orgId: Int, classroomNumber: Int) = ASSIGNMENTS_HREF_TEMPLATE.expand(orgId, classroomNumber)
78+
fun getAssignmentByNumberUri(orgId: Int, classroomNumber: Int, assignmentNumber: Int) =
79+
ASSIGNMENT_BY_NUMBER_HREF_TEMPLATE.expand(orgId, classroomNumber, assignmentNumber)
80+
81+
82+
// Deliveries
83+
const val DELIVERIES_HREF = "$ASSIGNMENT_BY_NUMBER_HREF/deliveries"
84+
const val DELIVERY_BY_NUMBER_HREF = "$DELIVERIES_HREF/{$DELIVERY_PARAM}"
85+
86+
val DELIVERIES_HREF_TEMPLATE = UriTemplate(DELIVERIES_HREF)
87+
val DELIVERY_BY_NUMBER_HREF_TEMPLATE = UriTemplate(DELIVERY_BY_NUMBER_HREF)
88+
89+
fun getDeliveriesUri(orgId: Int, classroomNumber: Int, assignmentNumber: Int) =
90+
DELIVERIES_HREF_TEMPLATE.expand(orgId, classroomNumber, assignmentNumber)
91+
fun getDeliveryByNumberUri(orgId: Int, classroomNumber: Int, assignmentNumber: Int, deliveryNumber: Int) =
92+
DELIVERY_BY_NUMBER_HREF_TEMPLATE.expand(orgId, classroomNumber, assignmentNumber, deliveryNumber)
93+
94+
95+
// User Classroom
96+
const val USERS_OF_CLASSROOM_HREF = "$CLASSROOM_BY_NUMBER_HREF/users"
97+
const val USER_OF_CLASSROOM_HREF = "$USERS_OF_CLASSROOM_HREF/{$USER_PARAM}"
98+
99+
val USER_OF_CLASSROOM_HREF_TEMPLATE = UriTemplate(USER_OF_CLASSROOM_HREF)
100+
101+
fun getUserOfClassroomUri(orgId: Int, classroomNumber: Int, userId: Int) =
102+
USER_OF_CLASSROOM_HREF_TEMPLATE.expand(orgId, classroomNumber, userId)
103+
104+
105+
// User Assignment
106+
const val USERS_OF_ASSIGNMENT_HREF = "$ASSIGNMENT_BY_NUMBER_HREF/users"
107+
const val USER_OF_ASSIGNMENT_HREF = "$USERS_OF_ASSIGNMENT_HREF/{$USER_PARAM}"
108+
109+
val USERS_OF_ASSIGNMENT_HREF_TEMPLATE = UriTemplate(USERS_OF_ASSIGNMENT_HREF)
110+
val USER_OF_ASSIGNMENT_HREF_TEMPLATE = UriTemplate(USER_OF_ASSIGNMENT_HREF)
111+
112+
fun getUsersOfAssignmentUri(orgId: Int, classroomNumber: Int, assignmentNumber: Int) =
113+
USERS_OF_ASSIGNMENT_HREF_TEMPLATE.expand(orgId, classroomNumber, assignmentNumber)
114+
fun getUserOfAssignmentUri(orgId: Int, classroomNumber: Int, assignmentNumber: Int, userId: Int) =
115+
USER_OF_ASSIGNMENT_HREF_TEMPLATE.expand(orgId, classroomNumber, assignmentNumber, userId)
116+
117+
118+
// User Deliveries
119+
const val DELIVERIES_OF_USER_HREF = "$USER_OF_ASSIGNMENT_HREF/deliveries"
120+
121+
val DELIVERIES_OF_USER_HREF_TEMPLATE = UriTemplate(DELIVERIES_OF_USER_HREF)
122+
123+
fun getDeliveriesOfUserUri(orgId: Int, classroomNumber: Int, assignmentNumber: Int, userId: Int) =
124+
DELIVERIES_OF_USER_HREF_TEMPLATE.expand(orgId, classroomNumber, assignmentNumber, userId)
125+
126+
127+
// Helpers
128+
fun createSirenLinkListForPagination(uri: URI, page: Int, limit: Int, collectionSize: Int): List<SirenLink> {
129+
val toReturn = mutableListOf(
130+
SirenLink(listOf("self"), UriTemplate("${uri}$PAGE_QUERY").expand(page, limit)),
131+
SirenLink(listOf("page"), hrefTemplate = "${uri}$PAGE_TEMPLATE_QUERY")
132+
)
133+
134+
if (page > 0 && collectionSize > 0)
135+
toReturn.add(
136+
SirenLink(
137+
listOf("previous"),
138+
UriTemplate("${uri}$PAGE_QUERY")
139+
.expand(page - 1, limit)
140+
)
141+
)
142+
143+
if (collectionSize > ((page + 1) * limit))
144+
toReturn.add(
145+
SirenLink(
146+
listOf("next"),
147+
UriTemplate("${uri}$PAGE_QUERY")
148+
.expand(page + 1, limit)
149+
)
150+
)
151+
152+
return toReturn
153+
}
154+
155+
fun URI.includeHost() = URI(SCHEME.toLowerCase(), HOST, this.path, this.query, this.fragment)
156+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.ionproject.codegarten.auth
2+
3+
import org.ionproject.codegarten.database.dto.User
4+
import org.ionproject.codegarten.exceptions.AuthorizationException
5+
import org.slf4j.LoggerFactory
6+
import org.springframework.util.Base64Utils
7+
8+
object AuthHeaderValidator {
9+
const val AUTH_SCHEME = "Bearer"
10+
const val AUTH_HEADER = "Authorization"
11+
12+
private val logger = LoggerFactory.getLogger(AuthHeaderValidator::class.java)
13+
14+
fun validate(header: String?, validateUserFunction: (token: String) -> User): User {
15+
if (header == null) {
16+
logger.info("Authorization header was not provided")
17+
throw AuthorizationException("Resource requires authentication")
18+
}
19+
if (!header.startsWith(AUTH_SCHEME, true)) {
20+
logger.info("Authorization header didn't follow the auth basic scheme")
21+
throw AuthorizationException("Invalid authorization scheme")
22+
}
23+
24+
// Get user credentials
25+
val token: String
26+
try {
27+
val credentials = header.drop(AUTH_SCHEME.length + 1).trim()
28+
token = String(Base64Utils.decodeFromString(credentials))
29+
} catch(ex: Exception) {
30+
logger.info("Could not get user credentials")
31+
throw AuthorizationException("Bad credentials")
32+
}
33+
34+
return validateUserFunction(token)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.ionproject.codegarten.controllers
2+
3+
import org.ionproject.codegarten.Routes.ERROR_HREF
4+
import org.ionproject.codegarten.responses.ProblemJson
5+
import org.ionproject.codegarten.responses.Response
6+
import org.ionproject.codegarten.responses.toResponseEntity
7+
import org.springframework.boot.web.servlet.error.ErrorController
8+
import org.springframework.http.HttpStatus
9+
import org.springframework.http.ResponseEntity
10+
import org.springframework.web.bind.annotation.GetMapping
11+
import org.springframework.web.bind.annotation.RequestAttribute
12+
import org.springframework.web.bind.annotation.RestController
13+
import javax.servlet.RequestDispatcher
14+
import javax.servlet.http.HttpServletRequest
15+
16+
@RestController
17+
class ErrorController : ErrorController {
18+
19+
@GetMapping(ERROR_HREF)
20+
fun handleError(
21+
request: HttpServletRequest,
22+
@RequestAttribute(name = RequestDispatcher.ERROR_STATUS_CODE) status: Int
23+
): ResponseEntity<Response> {
24+
val httpStatus = HttpStatus.valueOf(status)
25+
26+
return ProblemJson(
27+
httpStatus.name,
28+
httpStatus.name,
29+
httpStatus.value(),
30+
httpStatus.reasonPhrase,
31+
request.requestURI
32+
).toResponseEntity(httpStatus)
33+
}
34+
35+
override fun getErrorPath() = ERROR_HREF
36+
}

0 commit comments

Comments
 (0)