Skip to content

Commit a12696a

Browse files
v0.15.0 (see NEWS)
1 parent 1d15520 commit a12696a

Some content is hidden

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

43 files changed

+791
-592
lines changed

.gitlab-ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
image: openjdk:11.0.14.1-jdk
1+
image: eclipse-temurin:11
22

33
services:
44
- postgres:11

NEWS

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
1-
___ ___ ____ _____ ____ _ _ _______ ______
2-
/ _ \ / _ \| _ \| ____| _ \ | \ | | ____\ \ / / ___|
3-
| | | | | | | |_) | _| | |_) | | \| | _| \ \ /\ / /\___ \
4-
| |_| | |_| | _ <| |___| __/ | |\ | |___ \ V V / ___) |
5-
\___/ \___/|_| \_\_____|_| |_| \_|_____| \_/\_/ |____/
1+
2+
██████╗ ██████╗ ██████╗ ███████╗██████╗
3+
██╔═══██╗██╔═══██╗██╔══██╗██╔════╝██╔══██╗
4+
██║ ██║██║ ██║██████╔╝█████╗ ██████╔╝
5+
██║ ██║██║ ██║██╔══██╗██╔══╝ ██╔═══╝
6+
╚██████╔╝╚██████╔╝██║ ██║███████╗██║
7+
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝
8+
███╗ ██╗███████╗██╗ ██╗███████╗
9+
████╗ ██║██╔════╝██║ ██║██╔════╝
10+
██╔██╗ ██║█████╗ ██║ █╗ ██║███████╗
11+
██║╚██╗██║██╔══╝ ██║███╗██║╚════██║
12+
██║ ╚████║███████╗╚███╔███╔╝███████║
13+
╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚══════╝
14+
15+
v0.15.0 Sa 16. Sep 13:36:29 CEST 2023
16+
--------------------------------------------------------
17+
- Main features:
18+
- Change of database schema (not a user visible one, but
19+
warrants for a new version number)
20+
21+
- Minor improvements and changes:
22+
- Add "Clone case" button for logged-in users
23+
- Add icons to case buttons for logged-in users
24+
- Replace deprecated OpenJDK docker image with Eclipse Temurin image
25+
- Update to ScalaJS 1.13.2 and Scala 2.13.11
26+
- Update a lot of other library and plugin versions (too many to
27+
mention them all explicitly here; cf. a diff, if you care)
28+
- New light-weight wrapper around the ScalaJS networking code
29+
as to reduce dependencies to third-party networking libraries
30+
- Define access level for users to determine which resources
31+
(repositories, materia medicas) they have access to
32+
33+
- Bug fixes:
34+
- Opening of LARGE cases did not work
35+
- Edit new case dialog would not enable or disable the
36+
submit and cancel buttons appropriately
37+
- OOREP would often crash after any kind of network transmission
38+
error, whereas now it fails more gracefully
639

740
v0.14.2 Mo 15. Mai 18:31:05 CEST 2023
841
--------------------------------------------------------

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ intended for any production environments or the like without further modificatio
3939

4040
#### Prerequisites
4141

42-
* Java SDK >= 8
42+
* Java SDK versions 8 or 11
4343
* Scala Build Tool SBT >= 1.3.0
4444
* A PostgreSQL database server >= 9.6
4545

backend/app/org/multics/baueran/frep/backend/controllers/Get.scala

+21-21
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
5151
case None =>
5252
Logger.error("Get: login() not completed successfully: sending user to logout to be safe.")
5353
Redirect(sys.env.get("OOREP_URL_LOGOUT").getOrElse(""))
54-
case Some(uid) =>
55-
memberDao.increaseLoginCounter(uid)
56-
memberDao.setLastSeen(uid, new MyDate())
57-
Logger.debug(s"Get: login() completed for user ${uid.toString}.")
54+
case Some(member) =>
55+
memberDao.increaseLoginCounter(member.member_id)
56+
memberDao.setLastSeen(member.member_id, new MyDate())
57+
Logger.debug(s"Get: login() completed for user ${member.member_id.toString}.")
5858
Redirect(serverUrl(request))
5959
.withCookies(
60-
Cookie(CookieFields.id.toString, uid.toString, secure = true, httpOnly = false),
60+
Cookie(CookieFields.id.toString, member.member_id.toString, secure = true, httpOnly = false),
6161
Cookie(CookieFields.cookiePopupAccepted.toString, "1", secure = true, httpOnly = false)
6262
)
6363
}
@@ -99,13 +99,13 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
9999
def serve_static_html(page: String) = Action { implicit request: Request[AnyContent] =>
100100
page match {
101101
case "index" => Ok(views.html.index_landing(request))
102-
case "cookies" => Ok(views.html.index_static_content(request, views.html.partial.cookies.render, "OOREP - Privacy policy"))
103-
case "contact" => Ok(views.html.index_static_content(request, views.html.partial.contact.render, "OOREP - Contact"))
104-
case "datenschutz" => Ok(views.html.index_static_content(request, views.html.partial.datenschutz.render, "OOREP - Datenschutzerklärung"))
105-
case "faq" => Ok(views.html.index_static_content(request, views.html.partial.faq.render, "OOREP - Frequently asked questions and answers"))
106-
case "forgot_password" => Ok(views.html.index_static_content(request, views.html.partial.forgot_password.render, s"OOREP ${xml.Utility.escape("")} open online homeopathic repertory"))
107-
case "impressum" => Ok(views.html.index_static_content(request, views.html.partial.impressum.render, "OOREP - Impressum", "de"))
108-
case "register" => Ok(views.html.index_static_content(request, views.html.partial.register.render, "OOREP - Registration"))
102+
case "cookies" => Ok(views.html.index_static_content(request, views.html.partial.cookies.render(), "OOREP - Privacy policy"))
103+
case "contact" => Ok(views.html.index_static_content(request, views.html.partial.contact.render(), "OOREP - Contact"))
104+
case "datenschutz" => Ok(views.html.index_static_content(request, views.html.partial.datenschutz.render(), "OOREP - Datenschutzerklärung"))
105+
case "faq" => Ok(views.html.index_static_content(request, views.html.partial.faq.render(), "OOREP - Frequently asked questions and answers"))
106+
case "forgot_password" => Ok(views.html.index_static_content(request, views.html.partial.forgot_password.render(), s"OOREP ${xml.Utility.escape("")} open online homeopathic repertory"))
107+
case "impressum" => Ok(views.html.index_static_content(request, views.html.partial.impressum.render(), "OOREP - Impressum", "de"))
108+
case "register" => Ok(views.html.index_static_content(request, views.html.partial.register.render(), "OOREP - Registration"))
109109
case _ => NotFound(views.html.defaultpages.notFound("GET", page))
110110
}
111111
}
@@ -150,8 +150,8 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
150150

151151
def apiAuthenticate() = Action { request: Request[AnyContent] =>
152152
getAuthenticatedUser(request) match {
153-
case Some(uid) =>
154-
Ok(uid.toString)
153+
case Some(member) =>
154+
Ok(member.member_id.toString)
155155
case None =>
156156
val errStr = s"Get: apiAuthenticate(): User cannot be authenticated."
157157
Logger.error(errStr)
@@ -164,11 +164,11 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
164164
}
165165

166166
def apiAvailableRepertoriesAndRemedies() = Action { request: Request[AnyContent] =>
167-
Ok((repertoryDao.getRepsAndRemedies(getAuthenticatedUser(request) != None).asJson.toString))
167+
Ok((repertoryDao.getRepsAndRemedies(getAuthenticatedUser(request)).asJson.toString))
168168
}
169169

170170
def apiAvailableMateriaMedicasAndRemedies() = Action { request: Request[AnyContent] =>
171-
Ok(mmDao.getMMsAndRemedies(getAuthenticatedUser(request) != None).asJson.toString())
171+
Ok(mmDao.getMMsAndRemedies(getAuthenticatedUser(request)).asJson.toString())
172172
}
173173

174174
/**
@@ -180,7 +180,7 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
180180
val errStr = "Get: apiSecAvailableFiles() failed: not authenticated"
181181
Logger.error(errStr)
182182
Unauthorized(errStr)
183-
case Some(uid) =>
183+
case Some(_) =>
184184
if (!isUserAuthorized(request, memberId)) {
185185
val err = s"Get: apiSecAvailableFiles() failed: not authorised"
186186
Logger.error(err)
@@ -218,9 +218,9 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
218218

219219
def apiSecGetCase(caseId: String) = Action { request: Request[AnyContent] =>
220220
getAuthenticatedUser(request) match {
221-
case Some(uid) if (caseId.forall(_.isDigit)) => {
221+
case Some(member) if (caseId.forall(_.isDigit)) => {
222222
cazeDao.get(caseId.toInt) match {
223-
case Right(caze) if (caze.member_id == uid) =>
223+
case Right(caze) if (caze.member_id == member.member_id) =>
224224
if (!isUserAuthorized(request, caze.member_id)) {
225225
val err = s"Get: apiSecGetCase() failed: not authorised."
226226
Logger.error(err)
@@ -291,7 +291,7 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
291291
val cleanedUpAbbrev = repertoryAbbrev.replaceAll("[^0-9A-Za-z\\-]", "")
292292

293293
// Check if user is allowed to access the resource at all (might be Private or Protected and user not logged in)
294-
if (repertoryDao.getRepsAndRemedies(getAuthenticatedUser(request) != None).find(_.info.abbrev == cleanedUpAbbrev) == None) {
294+
if (repertoryDao.getRepsAndRemedies(getAuthenticatedUser(request)).find(_.info.abbrev == cleanedUpAbbrev) == None) {
295295
Logger.info(s"Get: apiLookupRep(abbrev: ${repertoryAbbrev}, symptom: ${symptom}, page: ${page}, remedy: ${remedyString}, weight: ${minWeight}): user not allowed to access ressource.")
296296
NoContent
297297
}
@@ -317,7 +317,7 @@ class Get @Inject()(cc: ControllerComponents, dbContext: DBContext) extends Abst
317317
val cleanedUpAbbrev = mmAbbrev.replaceAll("[^0-9A-Za-z\\-]", "")
318318

319319
// Check if user is allowed to access the resource at all (might be Private or Protected and user not logged in)
320-
if (mmDao.getMMsAndRemedies(getAuthenticatedUser(request) != None).find(_.mminfo.abbrev == cleanedUpAbbrev) == None) {
320+
if (mmDao.getMMsAndRemedies(getAuthenticatedUser(request)).find(_.mminfo.abbrev == cleanedUpAbbrev) == None) {
321321
Logger.info(s"Get: apiLookupMM(abbrev: ${mmAbbrev}, symptom: ${symptom}, page: ${page}, remedy: ${remedyString}): user not allowed to access ressource.")
322322
NoContent
323323
}

backend/app/org/multics/baueran/frep/backend/controllers/package.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.multics.baueran.frep.backend
22

33
import play.api.mvc._
44
import org.multics.baueran.frep.backend.dao.{CazeDao, EmailHistoryDao, FileDao, MMDao, MemberDao, PasswordChangeRequestDao, RepertoryDao}
5+
import org.multics.baueran.frep.shared.Member
56

67
package object controllers {
78

@@ -34,11 +35,11 @@ package object controllers {
3435
* as in the DB. This indicates that the user has prior logged in to the system.
3536
*/
3637

37-
def getAuthenticatedUser(request: Request[AnyContent]): Option[Int] = {
38+
def getAuthenticatedUser(request: Request[AnyContent]): Option[Member] = {
3839
request.headers.get("X-Remote-User") match {
3940
case Some(uidStr) if (uidStr.length > 0 && uidStr.forall(_.isDigit)) =>
4041
Logger.debug(s"getAuthenticatedUser: SUCCESS: uid: ${uidStr}.")
41-
return Some(uidStr.toInt)
42+
return memberDao.get(uidStr.toInt)
4243
case Some(uidStr) if (uidStr.length == 0) =>
4344
Logger.debug(s"getAuthenticatedUser: DEBUG: uid: ${uidStr}.")
4445
case _ =>
@@ -60,7 +61,7 @@ package object controllers {
6061

6162
def isUserAuthorized(request: Request[AnyContent], tries_to_access_data_of_member_id: Int) = {
6263
getAuthenticatedUser(request) match {
63-
case Some(uid) => uid == tries_to_access_data_of_member_id
64+
case Some(member) => member.member_id == tries_to_access_data_of_member_id
6465
case None => false
6566
}
6667
}

backend/app/org/multics/baueran/frep/backend/dao/CazeDao.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package org.multics.baueran.frep.backend.dao
33
import org.multics.baueran.frep._
44
import backend.db
55
import shared.{BetterString, CaseRubric, Caze}
6-
import io.getquill.{ActionReturning, Query, Update}
6+
import io.getquill.{ActionReturning, Query, Quoted, Update}
77

88
class CazeDao(dbContext: db.db.DBContext) {
99

@@ -34,7 +34,7 @@ class CazeDao(dbContext: db.db.DBContext) {
3434
// Insert case result IDs
3535
val rawQuery = quote {
3636
(id: Int, crs: List[Int]) =>
37-
infix"""UPDATE caze SET results=$crs WHERE id=$id"""
37+
sql"""UPDATE caze SET results=$crs WHERE id=$id"""
3838
.as[Update[Caze]]
3939
}
4040
run(rawQuery(lift(newId), lift(caseResultIds)))
@@ -59,7 +59,7 @@ class CazeDao(dbContext: db.db.DBContext) {
5959
else {
6060
val rawQuery = quote {
6161
// Notice the '#' in front for dynamic infix queries! It is, sort of, the alternative to lift(...).
62-
infix"""SELECT id, header, member_id, date_, description, results FROM caze WHERE id IN (#${ids.mkString(", ")})"""
62+
sql"""SELECT id, header, member_id, date_, description, results FROM caze WHERE id IN (#${ids.mkString(", ")})"""
6363
.as[Query[RawCaze]]
6464
}
6565

@@ -198,7 +198,7 @@ class CazeDao(dbContext: db.db.DBContext) {
198198
// Add case result ids to case
199199
val rawQuery = quote {
200200
(id: Int, crs: List[Int]) =>
201-
infix"""UPDATE caze SET results=results || $crs WHERE id=$id"""
201+
sql"""UPDATE caze SET results=results || $crs WHERE id=$id"""
202202
.as[Update[Caze]]
203203
}
204204
val numberOfUpdates = run(rawQuery(lift(caseId), lift(caseResultIds)))
@@ -228,7 +228,7 @@ class CazeDao(dbContext: db.db.DBContext) {
228228
val leftOverCaseResultIds: List[Int] = caze.results.toSet.filterNot(deletedCaseResultIds.toSet).asInstanceOf[Set[Int]].toList
229229
val rawQuery = quote {
230230
(id: Int, crs: List[Int]) =>
231-
infix"""UPDATE caze SET results=$crs WHERE id=$id"""
231+
sql"""UPDATE caze SET results=$crs WHERE id=$id"""
232232
.as[Update[Caze]]
233233
}
234234

backend/app/org/multics/baueran/frep/backend/dao/FileDao.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class FileDao(dbContext: db.db.DBContext) {
119119
// Add case id to file
120120
val rawQuery = quote {
121121
(localId: Int, localCazeId: Int) =>
122-
infix"""UPDATE file SET case_ids=case_ids || $localCazeId WHERE id=$localId"""
122+
sql"""UPDATE file SET case_ids=case_ids || $localCazeId WHERE id=$localId"""
123123
.as[Update[Caze]]
124124
}
125125
if (run(rawQuery(lift(fileId), lift(foundCase.id))) > 0) {

backend/app/org/multics/baueran/frep/backend/dao/MMDao.scala

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.multics.baueran.frep.backend.dao
22

33
import io.getquill.Query
4-
import org.multics.baueran.frep.shared.{HitsPerRemedy, MMAllSearchResults, MMAndRemedyIds, MMChapter, MMInfo, MMSearchResult, MMSection, MyDate, Remedies, Remedy, RemedyEntered, SearchTerms}
4+
import org.multics.baueran.frep.shared.{HitsPerRemedy, MMAllSearchResults, MMAndRemedyIds, MMChapter, MMInfo, MMSearchResult, MMSection, Member, MyDate, Remedies, Remedy, RemedyEntered, SearchTerms}
55
import org.multics.baueran.frep.backend.db
66
import org.multics.baueran.frep.shared.Defs.{ResourceAccessLvl, maxNumberOfResultsPerMMPage, maxNumberOfSymptoms}
77

@@ -19,7 +19,7 @@ class MMDao(dbContext: db.db.DBContext) {
1919
return List()
2020

2121
run {
22-
infix"""SELECT remedy.id, remedy.nameabbrev, remedy.namelong, remedy.namealt
22+
sql"""SELECT remedy.id, remedy.nameabbrev, remedy.namelong, remedy.namealt
2323
FROM remedy
2424
JOIN mmchapter ON remedy_id=remedy.id
2525
JOIN mminfo ON mminfo_id=mminfo.id AND mminfo.abbrev='#${cleanedAbbrev}'
@@ -37,7 +37,7 @@ class MMDao(dbContext: db.db.DBContext) {
3737
* So a translator class TmpMMsAndRemedies was necessary - and needs to be adapted, in
3838
* case the content of the other tables changes, of course.)
3939
*/
40-
def getMMsAndRemedies(isUserLoggedIn: Boolean): List[MMAndRemedyIds] = {
40+
def getMMsAndRemedies(loggedInMember: Option[Member]): List[MMAndRemedyIds] = {
4141
case class TmpMMsAndRemedies(id: Int,
4242
abbrev: String,
4343
lang: Option[String],
@@ -60,7 +60,7 @@ class MMDao(dbContext: db.db.DBContext) {
6060

6161
val tmpResults = run {
6262
quote {
63-
infix"""SELECT mminfo.id, mminfo.abbrev, mminfo.lang, mminfo.fulltitle, mminfo.authorlastname, mminfo.authorfirstname,
63+
sql"""SELECT mminfo.id, mminfo.abbrev, mminfo.lang, mminfo.fulltitle, mminfo.authorlastname, mminfo.authorfirstname,
6464
mminfo.publisher, mminfo.yearr, mminfo.license, mminfo.access, mminfo.displaytitle, ARRAY_AGG(remedy_id) remedy_ids
6565
FROM mmchapter
6666
JOIN mminfo ON mmchapter.mminfo_id = mminfo.id GROUP BY (mminfo.abbrev, mminfo.access)"""
@@ -74,10 +74,17 @@ class MMDao(dbContext: db.db.DBContext) {
7474
r.remedy_ids)
7575
)
7676

77-
if (isUserLoggedIn)
78-
tmpResults.filter(result => result.mminfo.access != ResourceAccessLvl.Private.toString)
79-
else
80-
tmpResults.filter(result => result.mminfo.access == ResourceAccessLvl.Public.toString || result.mminfo.access == ResourceAccessLvl.Default.toString)
77+
loggedInMember match {
78+
case Some(member) =>
79+
if (member.access.getOrElse("") == ResourceAccessLvl.Private.toString)
80+
tmpResults
81+
else if (member.access.getOrElse("") == ResourceAccessLvl.Protected.toString)
82+
tmpResults.filter(result => result.mminfo.access != ResourceAccessLvl.Private.toString)
83+
else
84+
tmpResults.filter(result => result.mminfo.access == ResourceAccessLvl.Public.toString || result.mminfo.access == ResourceAccessLvl.Default.toString)
85+
case None =>
86+
tmpResults.filter(result => result.mminfo.access == ResourceAccessLvl.Public.toString || result.mminfo.access == ResourceAccessLvl.Default.toString)
87+
}
8188
}
8289

8390
/**
@@ -193,7 +200,7 @@ class MMDao(dbContext: db.db.DBContext) {
193200
.join(query[MMInfo]).on({ case ((s, c), i) => i.id == c.mminfo_id && i.abbrev == lift(abbrev) })
194201
.join(query[Remedy]).on({ case (((s,c), i), r) => r.id == c.remedy_id &&
195202
( r.nameLong.toLowerCase.startsWith(lift(lowerRemedyName)) ||
196-
infix"""lower(array_to_string(${r.namealt}, ', '))""".as[String].like(lift(s"%${lowerRemedyName}%")) ) })
203+
sql"""lower(array_to_string(${r.namealt}, ', '))""".as[String].like(lift(s"%${lowerRemedyName}%")) ) })
197204
.filter { case (((s, c), i), r) =>
198205
s.content.getOrElse("").toLowerCase.like(lift(s"%${approximateSearchTerm}%")) || s.heading.getOrElse("").toLowerCase.like(lift(s"%${approximateSearchTerm}%"))
199206
}
@@ -215,7 +222,7 @@ class MMDao(dbContext: db.db.DBContext) {
215222
.join(query[MMInfo]).on({ case ((s, c), i) => i.id == c.mminfo_id && i.abbrev == lift(abbrev) })
216223
.join(query[Remedy]).on({ case (((s,c), i), r) => r.id == c.remedy_id &&
217224
( r.nameLong.toLowerCase.startsWith(lift(lowerRemedyName)) ||
218-
infix"""lower(array_to_string(${r.namealt}, ', '))""".as[String].like(lift(s"%${lowerRemedyName}%")) ) })
225+
sql"""lower(array_to_string(${r.namealt}, ', '))""".as[String].like(lift(s"%${lowerRemedyName}%")) ) })
219226
.filter(_ => true)
220227
}).collect { case (((s, c), i), r) => (s, c)}
221228
}

backend/app/org/multics/baueran/frep/backend/dao/MemberDao.scala

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,25 @@ class MemberDao(dbContext: db.db.DBContext) {
88

99
import dbContext._
1010

11-
private val tableMember = quote { querySchema[Member]("Member", _.member_id -> "member_id") }
11+
private val tableMember = quote { query[Member] }
1212

1313
// Stores the hash of `password` in the member table of database
1414
def updatePassword(password: String, id: Int) = {
1515
val rawInsert = quote {
16-
infix"""UPDATE member SET hash=(crypt('#${password}', gen_salt('bf'))) WHERE member_id=#${id}"""
16+
sql"""UPDATE member SET hash=(crypt('#${password}', gen_salt('bf'))) WHERE member_id=#${id}"""
1717
.as[Update[Member]]
1818
}
1919
run(rawInsert)
2020
}
2121

22-
def get(id: Int) = {
22+
def get(id: Int): Option[Member] = {
2323
val select = quote {
2424
tableMember.filter(_.member_id == lift(id))
2525
}
26-
run(select)
26+
run(select) match {
27+
case member :: Nil => Some(member)
28+
case _ => None
29+
}
2730
}
2831

2932
def getFromEmail(email: String) = {

0 commit comments

Comments
 (0)