Skip to content

Commit

Permalink
Merge v0.2024.006 into 'release'.
Browse files Browse the repository at this point in the history
  • Loading branch information
kajmagnus committed Sep 9, 2024
2 parents a941015 + 0c47139 commit 07ac81a
Show file tree
Hide file tree
Showing 129 changed files with 4,642 additions and 1,684 deletions.
2 changes: 1 addition & 1 deletion appsv/model/src/main/scala/com/debiki/core/Post.scala
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ case class Draft(
deletedAt: Option[When] = None,
topicType: Option[PageType] = None,
postType: Option[PostType] = None,
doAsAnon: Opt[WhichAnon],
doAsAnon: Opt[WhichAliasId],
title: String,
text: String) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ trait SiteTransaction { RENAME // to SiteTx — already started with a type Si

def insertAnonym(anonym: Anonym): U

def loadAnyAnon(userId: UserId, pageId: PageId, anonStatus: AnonStatus): Opt[Anonym]

def nextMemberId: UserId
def insertMember(user: UserInclDetails): Unit

Expand Down
1 change: 1 addition & 0 deletions appsv/model/src/main/scala/com/debiki/core/dao-db.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ object DbDao {
object BadPasswordException extends QuickMessageException("Bad password")
object UserDeletedException extends QuickMessageException("User deleted")

RENAME // to DuplicateActionEx?
case object DuplicateVoteException extends RuntimeException("Duplicate vote")

class PageNotFoundException(message: String) extends RuntimeException(message)
Expand Down
112 changes: 84 additions & 28 deletions appsv/model/src/main/scala/com/debiki/core/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -939,20 +939,6 @@ package object core {
}


/*
sealed abstract class AnonLevel(val IntVal: i32) { def toInt: i32 = IntVal }
object AnonLevel {
case object NotAnon extends AnonLevel(10)
case object AnonymPerPage extends AnonLevel(50)
def fromInt(value: i32): Opt[AnonLevel] = Some(value match {
case NotAnon.IntVal => NotAnon
case AnonymPerPage.IntVal => AnonymPerPage
case _ => return None
})
}*/



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


sealed abstract class WhichAnon() {
require(anySameAnonId.isDefined != anyNewAnonStatus.isDefined, "TyE6G0FM2TF3")
/** For before an alias has been looked up — we know only its id. Or,
* if it's a lazy-created anon, we don't know its id (doesn't yet exist),
* instead, we only know what type of anon it's going to be, that is, its
* future anon status (currently, either temporarily anonymous,
* for ideation, or permanently, for sensitive discussions).
*/
sealed abstract class WhichAliasId() {
// Remove later. [chk_alias_status]
require(anySameAliasId.isEmpty || anyAnonStatus.isEmpty, "TyE6G0FM2TF3")

def anyAnonStatus: Opt[AnonStatus]
def anySameAliasId: Opt[AnonId]
}


object WhichAliasId {

/** For doing sth as oneself (even if anonymity is the default) — "Yourself Mode". */
case object Oneself extends WhichAliasId {
def anySameAliasId: Opt[AnonId] = None
def anyAnonStatus: Opt[AnonStatus] = None
}

// Later: [pseudonyms_later]
//case class SamePseudonym(sameAliasId: PatId) extends WhichAliasId with SameAlias {
// def anySameAliasId: Opt[PatId] = Some(sameAliasId)
// def anyAnonStatus: Opt[AnonStatus] = None
//}

COULD // add anonStatus, error if mismatch? [chk_alias_status]
case class SameAnon(sameAnonId: PatId) extends WhichAliasId {
require(sameAnonId <= Pat.MaxAnonId, s"Not an anon id: $sameAnonId")
def anySameAliasId: Opt[AnonId] = Some(sameAnonId)
def anyAnonStatus: Opt[AnonStatus] = None
}

case class LazyCreatedAnon(anonStatus: AnonStatus) extends WhichAliasId {
require(anonStatus != AnonStatus.NotAnon, "WhichAliasId.anonStatus is NotAnon [TyE2M068G]")
def anySameAliasId: Opt[AnonId] = None
def anyAnonStatus: Opt[AnonStatus] = Some(anonStatus)
}
}


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

object WhichAnon {
case class NewAnon(anonStatus: AnonStatus) extends WhichAnon {
require(anonStatus != AnonStatus.NotAnon, "WhichAnon is NotAnon [TyE2MC06Y8G]")
override def anyNewAnonStatus: Opt[AnonStatus] = Some(anonStatus)

object WhichAliasPat {
// Later: [pseudonyms_later]
// Create a Pseudonym class? Pat / PatBr has unnecessary stuff, e.g. sso id.
//case class SamePseudonym(pseudonym: Pseudonym) extends WhichAliasPat {
// def anyPat: Opt[Pat] = Some(pseudonym)
//}

case class SameAnon(anon: Anonym) extends WhichAliasPat {
def anyPat: Opt[Pat] = Some(anon)
}

case class SameAsBefore(sameAnonId: PatId) extends WhichAnon {
override def anySameAnonId: Opt[AnonId] = Some(sameAnonId)
/** Reuses any already existing anonym with the same anon status,
* on the same page.
*
* If there're many, on the relevant page, then what? Throw an error?
* Can't happen, yet, because [one_anon_per_page].
*/
case class LazyCreatedAnon(anonStatus: AnonStatus) extends WhichAliasPat {
def anyPat: Opt[Pat] = None // might not yet exist
}

// Let's not support creating more than one anonym per user & page, for now.
//case class NewAnon(anonStatus: AnonStatus) extends WhichAliasPat {
// def anyPat: Opt[Pat] = None
//}
}


sealed abstract class AnyUserAndLevels {
def anyUser: Opt[Pat]
def trustLevel: TrustLevel
def threatLevel: ThreatLevel
}

/**
* @param user, (RENAME to patOrPseudonym?) — the id of the requester, can be a pseudonym. But not an anonym.
* @param trustLevel — if patOrPseudonym is a pseudonym, then this is the pseudonym's
Expand All @@ -1111,13 +1162,17 @@ package object core {
user: Pat,
trustLevel: TrustLevel,
threatLevel: ThreatLevel,
) {
) extends AnyUserAndLevels {
def anyUser = Some(user)
def id: UserId = user.id
def isStaff: Boolean = user.isStaff
def nameHashId: String = user.nameHashId
}

case class AnyUserAndThreatLevel(user: Option[Participant], threatLevel: ThreatLevel)
case class StrangerAndThreatLevel(threatLevel: ThreatLevel) extends AnyUserAndLevels {
def anyUser: Opt[Pat] = None
def trustLevel: TrustLevel = TrustLevel.Stranger
}


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


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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ case class PermsOnPages( // [exp] ok use. Missing, fine: may_see_private_flagge
mayCreatePage: Opt[Bo] = None,
mayPostComment: Opt[Bo] = None,
maySee: Opt[Bo] = None,
// Wants index: pages_i_authorid_catid_createdat_pageid
maySeeOwn: Opt[Bo] = None) {

// maySeeIfEmbeddedAlthoughLoginRequired [emb_login_req]
Expand Down
12 changes: 11 additions & 1 deletion appsv/model/src/main/scala/com/debiki/core/user.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,10 @@ trait MemberMaybeDetails {
}


trait Alias {
def aliasForPatId: PatId
}


case class Anonym(
id: AnonId,
Expand All @@ -1032,12 +1036,18 @@ case class Anonym(
anonForPatId: MembId,
anonOnPageId: PageId,
// deanonymizedById: Opt[MembId], // later
) extends Pat with GuestOrAnon with Someone {
) extends Pat with GuestOrAnon with Someone with Alias {

override def trueId2: TrueId = TrueId(id, anyTrueId = Some(anonForPatId))
def aliasForPatId = anonForPatId

def anyUsername: Opt[St] = None

// Not that much in use — client side code shows the anon status instead [anon_2_str]
// (e.g. "Temp Anonym" or "Aonymous"), in different languages. But is used in
// "Written by ..." in email notifications?
def nameOrUsername: St = "Anonym"

override def anyName: Opt[St] = Some(nameOrUsername)
override def usernameOrGuestName: St = nameOrUsername

Expand Down
15 changes: 15 additions & 0 deletions appsv/rdb/src/main/resources/db/migration/db-wip.sql
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ alter domain alnum_plusdashdot_arr_d add
-- Odd, last_approved_edit_at can be not null, also if approved_at is null.
-- Harmless but maybe surprising in the future.

-- For listing pages by someone in a specific category. Helpful, for categories where
-- one may post topics, but not see others' posts. That is:
-- PermsOnPages(
-- mayCreatePage = true,
-- mayPostComment = true,
-- maySee = false <——
-- maySeeOwn = true <——
-- ...)

create index pages_i_authorid_catid_createdat_pageid on pages3 (
site_id, author_id, category_id, created_at desc, page_id desc);

-- No longer needed. Same as pages_i_createdby_catid but only on: (site_id, author_id).
drop index dw2_pages_createdby__i;


--=============================================================================
-- Upload refs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@


alter table page_users3 add column prefer_alias_id_c pat_id_d;
-- +
-- fk deferred
-- ix

Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ trait DraftsSiteDaoMixin extends SiteTransaction {
locator.postId.orNullInt,
draft.postType.map(_.toInt).orNullInt,
locator.toUserId.orNullInt,
draft.doAsAnon.flatMap(_.anySameAnonId.map(_.toInt)).orNullInt,
draft.doAsAnon.flatMap(_.anyNewAnonStatus.map(_.toInt)).orNullInt,
draft.doAsAnon.flatMap(_.anySameAliasId.map(_.toInt)).orNullInt,
draft.doAsAnon.flatMap(_.anyAnonStatus.map(_.toInt)).orNullInt,
draft.title,
draft.text))
}
Expand Down Expand Up @@ -215,7 +215,7 @@ trait DraftsSiteDaoMixin extends SiteTransaction {

Draft(
byUserId = getInt(rs, "by_user_id"),
doAsAnon = parseWhichAnon(rs),
doAsAnon = parseWhichAliasId(rs),
draftNr = getInt(rs, "draft_nr"),
forWhat = draftLocator,
createdAt = getWhen(rs, "created_at"),
Expand All @@ -228,21 +228,26 @@ trait DraftsSiteDaoMixin extends SiteTransaction {
}


/** Sync w talkyard.server.parser.parseWhichAnonJson().
/** Sync w talkyard.server.parser.parseWhichAliasIdJson().
*/
def parseWhichAnon(rs: js.ResultSet): Opt[WhichAnon] = {
private def parseWhichAliasId(rs: js.ResultSet): Opt[WhichAliasId] = {
val sameAnonId = getOptInt(rs, "post_as_id_c")

// Would need to remember anonStatus in new_anon_status_c, to [chk_alias_status]
// be able to check if the alias still has the same status as when the user
// started composing the draft. (If different, could notify han.)
//
// PostgreSQL custom domain anonym_status_d has verified that the value is valid.
val newAnonStatus = AnonStatus.fromOptInt(getOptInt(rs, "new_anon_status_c"))
dieIf(sameAnonId.isDefined && newAnonStatus.isDefined, "TyE6023RAKJ5",
"Both post_as_id_c and new_anon_status_c non-null")
if (sameAnonId.isDefined) {
Some(WhichAnon.SameAsBefore(sameAnonId.get))
Some(WhichAliasId.SameAnon(sameAnonId.get))
}
else if (newAnonStatus.isDefined) {
val anonStatus = newAnonStatus.get
if (anonStatus == AnonStatus.NotAnon) return None
Some(WhichAnon.NewAnon(anonStatus))
Some(WhichAliasId.LazyCreatedAnon(anonStatus))
}
else {
None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1291,30 +1291,60 @@ trait PostsSiteDaoMixin extends SiteTransaction {
case vote: PostVote =>
insertPostActionImpl(
postId = vote.uniqueId, pageId = vote.pageId, postNr = vote.postNr,
actionType = vote.voteType, doerId = vote.doerId, doneAt = vote.doneAt)
actionType = vote.voteType, doerId = vote.doerId, doneAt = vote.doneAt,
manyOk = false)
case flag: PostFlag =>
insertPostActionImpl(
postId = flag.uniqueId, pageId = flag.pageId, postNr = flag.postNr,
actionType = flag.flagType, doerId = flag.doerId, doneAt = flag.doneAt)
actionType = flag.flagType, doerId = flag.doerId, doneAt = flag.doneAt,
manyOk = true)
case rel: PatNodeRel[_] =>
// This covers owner-of (or will owner-of be in pat_node_multi_rels_t?),
// author-of and assigned-to.
// (The other approach: PostVote and PostFlag, above, is deprecated.)
insertPostActionImpl(
postId = rel.uniqueId, pageId = rel.pageId, postNr = rel.postNr,
actionType = rel.relType, doerId = rel.fromPatId, doneAt = rel.addedAt)
actionType = rel.relType, doerId = rel.fromPatId, doneAt = rel.addedAt,
manyOk = false)
}
}


private def insertPostActionImpl(postId: PostId, pageId: PageId, postNr: PostNr,
actionType: PostActionType, doerId: PatIds, doneAt: When) {
val statement = """
actionType: PostActionType, doerId: PatIds, doneAt: When, manyOk: Bo) {

val subTypeOne: i32 = 1

// Has the same person done this already (e.g. voted), using another persona?
if (!manyOk) {
// Let's run a `select`, so we'll know for sure what's wrong. If we instead
// use `insert into ... where not exists (...)`, we can't know if 0 updated rows
// is because of duplicated actions, or a SQL query or values bug.
TESTS_MISSING // TyTALIVOTES
val query = s"""
select * from post_actions3
where site_id = ?
and to_post_id_c = ?
and rel_type_c = ?
and (from_pat_id_c = ? or from_true_id_c = ?)
and sub_type_c = $subTypeOne
-- Let's skip, for now — otherwise might run into conflicts, if
-- undoing the deletion of a vote?
-- and deleted_at is null
limit 1 """
val values = List(siteId.asAnyRef, postId.asAnyRef, toActionTypeInt(actionType),
doerId.trueId.asAnyRef, doerId.trueId.asAnyRef)
runQueryFindMany(query, values, rs => {
throw DbDao.DuplicateVoteException
})
}

val statement = s"""
insert into post_actions3(site_id, to_post_id_c, page_id, post_nr, rel_type_c,
from_pat_id_c, from_true_id_c,
created_at, sub_type_c)
values (?, ?, ?, ?, ?, ?, ?, ?, 1)
"""
values (?, ?, ?, ?, ?, ?, ?, ?, $subTypeOne) """

val values = List[AnyRef](siteId.asAnyRef, postId.asAnyRef, pageId, postNr.asAnyRef,
toActionTypeInt(actionType), doerId.pubId.asAnyRef,
doerId.anyTrueId.orNullInt32, doneAt.asTimestamp)
Expand Down
5 changes: 2 additions & 3 deletions appsv/rdb/src/main/scala/com/debiki/dao/rdb/RdbUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ object RdbUtil {

def createdAt = getWhen(rs, "u_created_at")
val emailNotfPrefs = {
if (isGuestId(userId))
if (isGuestId(userId) && anonStatus.isEmpty)
_toEmailNotfs(rs.getString("g_email_notfs"))
else
_toEmailNotfs(rs.getString("u_email_notfs"))
Expand All @@ -313,8 +313,7 @@ object RdbUtil {
createdAt = createdAt,
anonForPatId = getInt32(rs, "u_true_id_c"),
anonStatus = anonStatus.get,
anonOnPageId = getString(rs, "u_anon_on_page_id_st_c"),
)
anonOnPageId = getString(rs, "u_anon_on_page_id_st_c"))
}
else if (isGuestId(userId)) {
Guest(
Expand Down
Loading

0 comments on commit 07ac81a

Please sign in to comment.