-
-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creating stats/ page with reporting showing where all money goes within a closed system. Drafting DDD. #299
base: main
Are you sure you want to change the base?
Conversation
…ate efficient query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments. It looks fine aside from those.
@@ -52,7 +52,11 @@ func ListMarketsHandler(w http.ResponseWriter, r *http.Request) { | |||
marketVolume := marketmath.GetMarketVolume(bets) | |||
lastProbability := probabilityChanges[len(probabilityChanges)-1].Probability | |||
|
|||
creatorInfo := usersHandlers.GetPublicUserInfo(db, market.CreatorUsername) | |||
creatorInfo, err := usersHandlers.GetPublicUserInfo(db, market.CreatorUsername) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this actually return a 400 or is it really a 404? Digging into the GetPublicUserInfo function it appears both are possible. For now we can handle this as is, but there should be a TODO or something to bubble up the error and handle it accordingly. Admittedly I don't know what errors GetUserByUsername does return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the market must have a creator username associated with it, so if an error is received, what should or process be for handling it? I don't think failing is the right answer here, but I'm not sure what an effective retry policy would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this might be a common issue if the creator decides to delete their account. We could create a null user to handle this case or just prevent account deletion unless the user releases the market manually whether by deleting the market or changing "ownership"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
404 Error Handling: You are correct that it should be a 404 error when a user is not found, and I will update the logic to reflect that.
-
Immutable Usernames for Traceability: Usernames should be immutable to maintain traceability of market activities. Allowing users to delete their accounts would remove critical records of the markets they created and how they resolved. Instead, we can implement an archiving system that retains the username but removes sensitive personal information like the display name. This would help prevent users from creating unreliable markets and then disappearing without accountability, ensuring the integrity of the platform.
Put together a decision ticket about this here: https://github.com/orgs/openpredictionmarkets/projects/9/views/1?pane=issue&itemId=80893304
Joins(query string, args ...interface{}) Database | ||
Scan(dest interface{}) Database | ||
SubQuery() *gorm.DB | ||
Raw(sql string, values ...interface{}) Database |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably remove this if the intention is to limit functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interfaces should generally be small, this could be addressed by having something like:
type DatabaseModeler interface {
Model(value interface{}) Database
}
This would then be used anywhere a Model
function is needed. This article provides some further reading on why and how this should be done: https://medium.com/@meeusdylan/go-interfaces-keep-them-small-f148ae200d6b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After looking back over this, I'm not sure about how we should be approaching this in general. What benefits do we get from this organization? i.e. what does a Database interface give us overall?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ajlacey if the intention is to restrict API access to the ORM I understand the intention of this object, but if we just allow public access to the Raw function, then you can run any command on the DB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of feedback here, but I think we should go in this order:
- Do we need the repository package/database abstraction? If so, what benefits does it give us?
- Should the
repository
package be a separate package or just built on top of the models that are stored in our database type? I think they should be together. - We should look at updating the
User
type to be composed of its public and private types for convenience. - We should avoid introducing placeholder code. It doesn't give us any functionality and just provides 'dead' code.
Retrospective: I think if we started at 1, we'd be able to decide what that should look like and have an isolated PR just focused on those changes. I think there's also a lot going on here that doesn't appear to be related to creating the stats page (indicative of scope creep). A lot of the new stats code doesn't use the setup config dynamically, which makes it impossible to test the stats code for different scenarios that could arise in the system based on configurations.
## 3. Data Conventions | ||
|
||
* The entire application should be as stateless as possible, meaning we have a one-way writeable database of users, markets, bets and calculate all relevant states of the application from as few possible columns within those models. | ||
* We should separate the data logic from the business logic as much as possible with a Domain Driven Design (DDD), meaning we have a repository/ directory which is designed to be the central location to keep functions that extract data from the databases. This should ideally help slow the growth of the codebase over time and keep data extraction more testable, which should make our startless architecture more reliable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo stateless/startless:
which should make our startless architecture more reliable.
if errors.Is(err, gorm.ErrRecordNotFound) { | ||
http.Error(w, "User not found", http.StatusNotFound) | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if a different record type isn't found? Why do we only use message in one of the cases here?
Do we need a separate error handler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I seconded this in my review.
if err != nil { | ||
// Check if the error is because the user was not found | ||
if errors.Is(err, gorm.ErrRecordNotFound) { | ||
// User not found, return 404 Not Found | ||
http.Error(w, "User not found", http.StatusNotFound) | ||
} else { | ||
// Internal server error | ||
http.Error(w, "Internal server error", http.StatusInternalServerError) | ||
// Optionally, log the error for debugging purposes | ||
log.Printf("Failed to get user info: %v", err) | ||
} | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to return to: This feels very similar to the error handler, and I suspect is indicative that the handler isn't necessary.
db.Where("username = ?", username).First(&user) | ||
user, err := repo.GetUserByUsername(username) | ||
if err != nil { | ||
return PublicUserType{}, fmt.Errorf("failed to get user by username: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning an empty struct, which will initialize the default zero values, we should return nil, err.
This prevents us from using values that are not initialized in the event that someone calling this function doesn't check the error.
This change will require the return type to be updated to *PublicUserType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These file names are redundant <type>_repository.go
should just be <type>.go
publicInfo, err := GetPublicUserInfo(db, username) | ||
if err != nil { | ||
helpers.HandleError(w, err, "Error fetching public user info") | ||
return | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this introduce a new helper to use when the implementation can be done more clearly here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we can remove this file package, but if not we should rename this package: https://go.dev/blog/package-names#bad-package-names
http.Error(w, "Can't retrieve user public info.", http.StatusBadRequest) | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the above, if we do return an error here, it should be an internal server error, the request wasn't malformed, the server just failed to retrieve user info.
@@ -52,7 +52,11 @@ func ListMarketsHandler(w http.ResponseWriter, r *http.Request) { | |||
marketVolume := marketmath.GetMarketVolume(bets) | |||
lastProbability := probabilityChanges[len(probabilityChanges)-1].Probability | |||
|
|||
creatorInfo := usersHandlers.GetPublicUserInfo(db, market.CreatorUsername) | |||
creatorInfo, err := usersHandlers.GetPublicUserInfo(db, market.CreatorUsername) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the market must have a creator username associated with it, so if an error is received, what should or process be for handling it? I don't think failing is the right answer here, but I'm not sure what an effective retry policy would be.
backend/handlers/stats/revenue.go
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of these functions have a dependency on the setup config, but don't inject that dependency into their functionality. This needs to be fixed to test these helpers properly.
Thinking more on this, the PR should be broken down, not just because it touches a lot of files, there are multiple objectives. |
Put together a ticket on point 1. Other points are separate because I'll remove the extra, non-used functions and we could take care of Public/Private user model in a separate ticket. In fact I'll put another ticket together for that right now as well. |
api/v0/stats
which reports financial stats for a given instance of SocialPredict.Results of my test on the stats/ api
These amounts are expected. I didn't write a test for this yet.