Skip to content

Commit 07ac81a

Browse files
committed
Merge v0.2024.006 into 'release'.
2 parents a941015 + 0c47139 commit 07ac81a

File tree

129 files changed

+4642
-1684
lines changed

Some content is hidden

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

129 files changed

+4642
-1684
lines changed

appsv/model/src/main/scala/com/debiki/core/Post.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ case class Draft(
354354
deletedAt: Option[When] = None,
355355
topicType: Option[PageType] = None,
356356
postType: Option[PostType] = None,
357-
doAsAnon: Opt[WhichAnon],
357+
doAsAnon: Opt[WhichAliasId],
358358
title: String,
359359
text: String) {
360360

appsv/model/src/main/scala/com/debiki/core/SiteTransaction.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ trait SiteTransaction { RENAME // to SiteTx — already started with a type Si
494494

495495
def insertAnonym(anonym: Anonym): U
496496

497+
def loadAnyAnon(userId: UserId, pageId: PageId, anonStatus: AnonStatus): Opt[Anonym]
498+
497499
def nextMemberId: UserId
498500
def insertMember(user: UserInclDetails): Unit
499501

appsv/model/src/main/scala/com/debiki/core/dao-db.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ object DbDao {
6969
object BadPasswordException extends QuickMessageException("Bad password")
7070
object UserDeletedException extends QuickMessageException("User deleted")
7171

72+
RENAME // to DuplicateActionEx?
7273
case object DuplicateVoteException extends RuntimeException("Duplicate vote")
7374

7475
class PageNotFoundException(message: String) extends RuntimeException(message)

appsv/model/src/main/scala/com/debiki/core/package.scala

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -939,20 +939,6 @@ package object core {
939939
}
940940

941941

942-
/*
943-
sealed abstract class AnonLevel(val IntVal: i32) { def toInt: i32 = IntVal }
944-
object AnonLevel {
945-
case object NotAnon extends AnonLevel(10)
946-
case object AnonymPerPage extends AnonLevel(50)
947-
948-
def fromInt(value: i32): Opt[AnonLevel] = Some(value match {
949-
case NotAnon.IntVal => NotAnon
950-
case AnonymPerPage.IntVal => AnonymPerPage
951-
case _ => return None
952-
})
953-
}*/
954-
955-
956942

957943
/** A bitfield. Currently only None, 65535 = IsAnonOnlySelfCanDeanon
958944
* and 2097151 = IsAnonCanAutoDeanon are supported.
@@ -1080,27 +1066,92 @@ package object core {
10801066
}
10811067

10821068

1083-
sealed abstract class WhichAnon() {
1084-
require(anySameAnonId.isDefined != anyNewAnonStatus.isDefined, "TyE6G0FM2TF3")
1069+
/** For before an alias has been looked up — we know only its id. Or,
1070+
* if it's a lazy-created anon, we don't know its id (doesn't yet exist),
1071+
* instead, we only know what type of anon it's going to be, that is, its
1072+
* future anon status (currently, either temporarily anonymous,
1073+
* for ideation, or permanently, for sensitive discussions).
1074+
*/
1075+
sealed abstract class WhichAliasId() {
1076+
// Remove later. [chk_alias_status]
1077+
require(anySameAliasId.isEmpty || anyAnonStatus.isEmpty, "TyE6G0FM2TF3")
1078+
1079+
def anyAnonStatus: Opt[AnonStatus]
1080+
def anySameAliasId: Opt[AnonId]
1081+
}
1082+
1083+
1084+
object WhichAliasId {
1085+
1086+
/** For doing sth as oneself (even if anonymity is the default) — "Yourself Mode". */
1087+
case object Oneself extends WhichAliasId {
1088+
def anySameAliasId: Opt[AnonId] = None
1089+
def anyAnonStatus: Opt[AnonStatus] = None
1090+
}
1091+
1092+
// Later: [pseudonyms_later]
1093+
//case class SamePseudonym(sameAliasId: PatId) extends WhichAliasId with SameAlias {
1094+
// def anySameAliasId: Opt[PatId] = Some(sameAliasId)
1095+
// def anyAnonStatus: Opt[AnonStatus] = None
1096+
//}
1097+
1098+
COULD // add anonStatus, error if mismatch? [chk_alias_status]
1099+
case class SameAnon(sameAnonId: PatId) extends WhichAliasId {
1100+
require(sameAnonId <= Pat.MaxAnonId, s"Not an anon id: $sameAnonId")
1101+
def anySameAliasId: Opt[AnonId] = Some(sameAnonId)
1102+
def anyAnonStatus: Opt[AnonStatus] = None
1103+
}
1104+
1105+
case class LazyCreatedAnon(anonStatus: AnonStatus) extends WhichAliasId {
1106+
require(anonStatus != AnonStatus.NotAnon, "WhichAliasId.anonStatus is NotAnon [TyE2M068G]")
1107+
def anySameAliasId: Opt[AnonId] = None
1108+
def anyAnonStatus: Opt[AnonStatus] = Some(anonStatus)
1109+
}
1110+
}
1111+
10851112

1086-
// Either ...
1087-
def anyNewAnonStatus: Opt[AnonStatus] = None
1088-
// ... or.
1089-
def anySameAnonId: Opt[AnonId] = None
1113+
/** For after the alias has been looked up by any id, when we have an Anonym or Pseudonym,
1114+
* not just an id. (Or still just a to-be-lazy-created anonym, with a future anon status.)
1115+
*/
1116+
sealed trait WhichAliasPat {
1117+
def anyPat: Opt[Pat]
10901118
}
10911119

1092-
object WhichAnon {
1093-
case class NewAnon(anonStatus: AnonStatus) extends WhichAnon {
1094-
require(anonStatus != AnonStatus.NotAnon, "WhichAnon is NotAnon [TyE2MC06Y8G]")
1095-
override def anyNewAnonStatus: Opt[AnonStatus] = Some(anonStatus)
1120+
1121+
object WhichAliasPat {
1122+
// Later: [pseudonyms_later]
1123+
// Create a Pseudonym class? Pat / PatBr has unnecessary stuff, e.g. sso id.
1124+
//case class SamePseudonym(pseudonym: Pseudonym) extends WhichAliasPat {
1125+
// def anyPat: Opt[Pat] = Some(pseudonym)
1126+
//}
1127+
1128+
case class SameAnon(anon: Anonym) extends WhichAliasPat {
1129+
def anyPat: Opt[Pat] = Some(anon)
10961130
}
10971131

1098-
case class SameAsBefore(sameAnonId: PatId) extends WhichAnon {
1099-
override def anySameAnonId: Opt[AnonId] = Some(sameAnonId)
1132+
/** Reuses any already existing anonym with the same anon status,
1133+
* on the same page.
1134+
*
1135+
* If there're many, on the relevant page, then what? Throw an error?
1136+
* Can't happen, yet, because [one_anon_per_page].
1137+
*/
1138+
case class LazyCreatedAnon(anonStatus: AnonStatus) extends WhichAliasPat {
1139+
def anyPat: Opt[Pat] = None // might not yet exist
11001140
}
1141+
1142+
// Let's not support creating more than one anonym per user & page, for now.
1143+
//case class NewAnon(anonStatus: AnonStatus) extends WhichAliasPat {
1144+
// def anyPat: Opt[Pat] = None
1145+
//}
11011146
}
11021147

11031148

1149+
sealed abstract class AnyUserAndLevels {
1150+
def anyUser: Opt[Pat]
1151+
def trustLevel: TrustLevel
1152+
def threatLevel: ThreatLevel
1153+
}
1154+
11041155
/**
11051156
* @param user, (RENAME to patOrPseudonym?) — the id of the requester, can be a pseudonym. But not an anonym.
11061157
* @param trustLevel — if patOrPseudonym is a pseudonym, then this is the pseudonym's
@@ -1111,13 +1162,17 @@ package object core {
11111162
user: Pat,
11121163
trustLevel: TrustLevel,
11131164
threatLevel: ThreatLevel,
1114-
) {
1165+
) extends AnyUserAndLevels {
1166+
def anyUser = Some(user)
11151167
def id: UserId = user.id
11161168
def isStaff: Boolean = user.isStaff
11171169
def nameHashId: String = user.nameHashId
11181170
}
11191171

1120-
case class AnyUserAndThreatLevel(user: Option[Participant], threatLevel: ThreatLevel)
1172+
case class StrangerAndThreatLevel(threatLevel: ThreatLevel) extends AnyUserAndLevels {
1173+
def anyUser: Opt[Pat] = None
1174+
def trustLevel: TrustLevel = TrustLevel.Stranger
1175+
}
11211176

11221177

11231178
sealed trait OrderBy { def isDescending: Boolean = false }
@@ -2057,6 +2112,7 @@ package object core {
20572112

20582113

20592114
implicit class RichBoolean(underlying: Boolean) {
2115+
// (For find-by-similar-name: "oneIfTrue".)
20602116
def toZeroOne: i32 = if (underlying) 1 else 0
20612117
}
20622118

appsv/model/src/main/scala/com/debiki/core/permissions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ case class PermsOnPages( // [exp] ok use. Missing, fine: may_see_private_flagge
123123
mayCreatePage: Opt[Bo] = None,
124124
mayPostComment: Opt[Bo] = None,
125125
maySee: Opt[Bo] = None,
126+
// Wants index: pages_i_authorid_catid_createdat_pageid
126127
maySeeOwn: Opt[Bo] = None) {
127128

128129
// maySeeIfEmbeddedAlthoughLoginRequired [emb_login_req]

appsv/model/src/main/scala/com/debiki/core/user.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,10 @@ trait MemberMaybeDetails {
10241024
}
10251025

10261026

1027+
trait Alias {
1028+
def aliasForPatId: PatId
1029+
}
1030+
10271031

10281032
case class Anonym(
10291033
id: AnonId,
@@ -1032,12 +1036,18 @@ case class Anonym(
10321036
anonForPatId: MembId,
10331037
anonOnPageId: PageId,
10341038
// deanonymizedById: Opt[MembId], // later
1035-
) extends Pat with GuestOrAnon with Someone {
1039+
) extends Pat with GuestOrAnon with Someone with Alias {
10361040

10371041
override def trueId2: TrueId = TrueId(id, anyTrueId = Some(anonForPatId))
1042+
def aliasForPatId = anonForPatId
10381043

10391044
def anyUsername: Opt[St] = None
1045+
1046+
// Not that much in use — client side code shows the anon status instead [anon_2_str]
1047+
// (e.g. "Temp Anonym" or "Aonymous"), in different languages. But is used in
1048+
// "Written by ..." in email notifications?
10401049
def nameOrUsername: St = "Anonym"
1050+
10411051
override def anyName: Opt[St] = Some(nameOrUsername)
10421052
override def usernameOrGuestName: St = nameOrUsername
10431053

appsv/rdb/src/main/resources/db/migration/db-wip.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ alter domain alnum_plusdashdot_arr_d add
6464
-- Odd, last_approved_edit_at can be not null, also if approved_at is null.
6565
-- Harmless but maybe surprising in the future.
6666

67+
-- For listing pages by someone in a specific category. Helpful, for categories where
68+
-- one may post topics, but not see others' posts. That is:
69+
-- PermsOnPages(
70+
-- mayCreatePage = true,
71+
-- mayPostComment = true,
72+
-- maySee = false <——
73+
-- maySeeOwn = true <——
74+
-- ...)
75+
76+
create index pages_i_authorid_catid_createdat_pageid on pages3 (
77+
site_id, author_id, category_id, created_at desc, page_id desc);
78+
79+
-- No longer needed. Same as pages_i_createdby_catid but only on: (site_id, author_id).
80+
drop index dw2_pages_createdby__i;
81+
6782

6883
--=============================================================================
6984
-- Upload refs
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
3+
alter table page_users3 add column prefer_alias_id_c pat_id_d;
4+
-- +
5+
-- fk deferred
6+
-- ix
7+

appsv/rdb/src/main/scala/com/debiki/dao/rdb/DraftsSiteDaoMixin.scala

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ trait DraftsSiteDaoMixin extends SiteTransaction {
111111
locator.postId.orNullInt,
112112
draft.postType.map(_.toInt).orNullInt,
113113
locator.toUserId.orNullInt,
114-
draft.doAsAnon.flatMap(_.anySameAnonId.map(_.toInt)).orNullInt,
115-
draft.doAsAnon.flatMap(_.anyNewAnonStatus.map(_.toInt)).orNullInt,
114+
draft.doAsAnon.flatMap(_.anySameAliasId.map(_.toInt)).orNullInt,
115+
draft.doAsAnon.flatMap(_.anyAnonStatus.map(_.toInt)).orNullInt,
116116
draft.title,
117117
draft.text))
118118
}
@@ -215,7 +215,7 @@ trait DraftsSiteDaoMixin extends SiteTransaction {
215215

216216
Draft(
217217
byUserId = getInt(rs, "by_user_id"),
218-
doAsAnon = parseWhichAnon(rs),
218+
doAsAnon = parseWhichAliasId(rs),
219219
draftNr = getInt(rs, "draft_nr"),
220220
forWhat = draftLocator,
221221
createdAt = getWhen(rs, "created_at"),
@@ -228,21 +228,26 @@ trait DraftsSiteDaoMixin extends SiteTransaction {
228228
}
229229

230230

231-
/** Sync w talkyard.server.parser.parseWhichAnonJson().
231+
/** Sync w talkyard.server.parser.parseWhichAliasIdJson().
232232
*/
233-
def parseWhichAnon(rs: js.ResultSet): Opt[WhichAnon] = {
233+
private def parseWhichAliasId(rs: js.ResultSet): Opt[WhichAliasId] = {
234234
val sameAnonId = getOptInt(rs, "post_as_id_c")
235+
236+
// Would need to remember anonStatus in new_anon_status_c, to [chk_alias_status]
237+
// be able to check if the alias still has the same status as when the user
238+
// started composing the draft. (If different, could notify han.)
239+
//
235240
// PostgreSQL custom domain anonym_status_d has verified that the value is valid.
236241
val newAnonStatus = AnonStatus.fromOptInt(getOptInt(rs, "new_anon_status_c"))
237242
dieIf(sameAnonId.isDefined && newAnonStatus.isDefined, "TyE6023RAKJ5",
238243
"Both post_as_id_c and new_anon_status_c non-null")
239244
if (sameAnonId.isDefined) {
240-
Some(WhichAnon.SameAsBefore(sameAnonId.get))
245+
Some(WhichAliasId.SameAnon(sameAnonId.get))
241246
}
242247
else if (newAnonStatus.isDefined) {
243248
val anonStatus = newAnonStatus.get
244249
if (anonStatus == AnonStatus.NotAnon) return None
245-
Some(WhichAnon.NewAnon(anonStatus))
250+
Some(WhichAliasId.LazyCreatedAnon(anonStatus))
246251
}
247252
else {
248253
None

appsv/rdb/src/main/scala/com/debiki/dao/rdb/PostsSiteDaoMixin.scala

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,30 +1291,60 @@ trait PostsSiteDaoMixin extends SiteTransaction {
12911291
case vote: PostVote =>
12921292
insertPostActionImpl(
12931293
postId = vote.uniqueId, pageId = vote.pageId, postNr = vote.postNr,
1294-
actionType = vote.voteType, doerId = vote.doerId, doneAt = vote.doneAt)
1294+
actionType = vote.voteType, doerId = vote.doerId, doneAt = vote.doneAt,
1295+
manyOk = false)
12951296
case flag: PostFlag =>
12961297
insertPostActionImpl(
12971298
postId = flag.uniqueId, pageId = flag.pageId, postNr = flag.postNr,
1298-
actionType = flag.flagType, doerId = flag.doerId, doneAt = flag.doneAt)
1299+
actionType = flag.flagType, doerId = flag.doerId, doneAt = flag.doneAt,
1300+
manyOk = true)
12991301
case rel: PatNodeRel[_] =>
13001302
// This covers owner-of (or will owner-of be in pat_node_multi_rels_t?),
13011303
// author-of and assigned-to.
13021304
// (The other approach: PostVote and PostFlag, above, is deprecated.)
13031305
insertPostActionImpl(
13041306
postId = rel.uniqueId, pageId = rel.pageId, postNr = rel.postNr,
1305-
actionType = rel.relType, doerId = rel.fromPatId, doneAt = rel.addedAt)
1307+
actionType = rel.relType, doerId = rel.fromPatId, doneAt = rel.addedAt,
1308+
manyOk = false)
13061309
}
13071310
}
13081311

13091312

13101313
private def insertPostActionImpl(postId: PostId, pageId: PageId, postNr: PostNr,
1311-
actionType: PostActionType, doerId: PatIds, doneAt: When) {
1312-
val statement = """
1314+
actionType: PostActionType, doerId: PatIds, doneAt: When, manyOk: Bo) {
1315+
1316+
val subTypeOne: i32 = 1
1317+
1318+
// Has the same person done this already (e.g. voted), using another persona?
1319+
if (!manyOk) {
1320+
// Let's run a `select`, so we'll know for sure what's wrong. If we instead
1321+
// use `insert into ... where not exists (...)`, we can't know if 0 updated rows
1322+
// is because of duplicated actions, or a SQL query or values bug.
1323+
TESTS_MISSING // TyTALIVOTES
1324+
val query = s"""
1325+
select * from post_actions3
1326+
where site_id = ?
1327+
and to_post_id_c = ?
1328+
and rel_type_c = ?
1329+
and (from_pat_id_c = ? or from_true_id_c = ?)
1330+
and sub_type_c = $subTypeOne
1331+
-- Let's skip, for now — otherwise might run into conflicts, if
1332+
-- undoing the deletion of a vote?
1333+
-- and deleted_at is null
1334+
limit 1 """
1335+
val values = List(siteId.asAnyRef, postId.asAnyRef, toActionTypeInt(actionType),
1336+
doerId.trueId.asAnyRef, doerId.trueId.asAnyRef)
1337+
runQueryFindMany(query, values, rs => {
1338+
throw DbDao.DuplicateVoteException
1339+
})
1340+
}
1341+
1342+
val statement = s"""
13131343
insert into post_actions3(site_id, to_post_id_c, page_id, post_nr, rel_type_c,
13141344
from_pat_id_c, from_true_id_c,
13151345
created_at, sub_type_c)
1316-
values (?, ?, ?, ?, ?, ?, ?, ?, 1)
1317-
"""
1346+
values (?, ?, ?, ?, ?, ?, ?, ?, $subTypeOne) """
1347+
13181348
val values = List[AnyRef](siteId.asAnyRef, postId.asAnyRef, pageId, postNr.asAnyRef,
13191349
toActionTypeInt(actionType), doerId.pubId.asAnyRef,
13201350
doerId.anyTrueId.orNullInt32, doneAt.asTimestamp)

0 commit comments

Comments
 (0)