Skip to content

Commit 75ae0b7

Browse files
authored
Merge branch 'main' into fix_janushytte_info
2 parents d2f58ec + d0d1c5b commit 75ae0b7

File tree

48 files changed

+1776
-9636
lines changed

Some content is hidden

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

48 files changed

+1776
-9636
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
APP_ENV=production
105105
106106
- name: Upload artifact
107-
uses: actions/upload-artifact@v3
107+
uses: actions/upload-artifact@v4
108108
with:
109109
name: ${{ env.dockerfile }}
110110
path: /tmp/${{ env.dockerfile }}.tar
@@ -131,7 +131,7 @@ jobs:
131131
uses: actions/checkout@v4
132132

133133
- name: Download image from previous job
134-
uses: actions/download-artifact@v3
134+
uses: actions/download-artifact@v4
135135
with:
136136
name: backend-production
137137
path: /tmp
@@ -222,7 +222,7 @@ jobs:
222222
APP_ENV=test
223223
224224
- name: Upload artifact
225-
uses: actions/upload-artifact@v3
225+
uses: actions/upload-artifact@v4
226226
with:
227227
name: ${{ env.dockerfile }}
228228
path: /tmp/${{ env.dockerfile }}.tar
@@ -244,7 +244,7 @@ jobs:
244244
uses: actions/checkout@v4
245245

246246
- name: Download image from previous job
247-
uses: actions/download-artifact@v3
247+
uses: actions/download-artifact@v4
248248
with:
249249
path: /tmp
250250

@@ -262,7 +262,7 @@ jobs:
262262
run: docker compose -f docker-compose.integration.yml up -d
263263

264264
- name: Run Cypress
265-
uses: cypress-io/github-action@v6.7.6
265+
uses: cypress-io/github-action@v6.7.8
266266
with:
267267
record: true
268268
parallel: true

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,75 @@ An outline of how a developer may work with this project:
561561
- Respond to any questions or concerns they may have with your Pull Request
562562
- If an automatic test fails, click `Details` on it to see what went wrong in the logs
563563
- Once your Pull Request is approved, and the tests pass, you can merge it - now your changes are live!
564+
### Comprehenceive Api and Backend Models Guide
565+
566+
#### Making a model (changes to the database)
567+
568+
The models.py file is essentially a description of what should be stored in our database. Types.py tells graphene, the service we use to autogenerate a ton of stuff what types the database has.
569+
570+
If you are adding a brand new models.py file make sure to add your changes to LOCAL_APPS in the base.py of our django settings. In order to make the migrations work you will also need a migrations folder containing a file named **init**.py. The file init file is empty but nessecary.
571+
572+
After changes have been made to thease files you need to push them to the database. This is done first by making the migrations using `docker compose exec backend python manage.py makemigrations` After the migrations are made you migrate useing `docker compose exec backend python manage.py migrate`. For the changes to show up in the admin panel you will need to restart docker
573+
574+
#### Making a query
575+
576+
A query is a request for data made to the backend by the frontend. The following is a guide on how to make queries.
577+
578+
##### Queries in Backend
579+
580+
All queries have a respective resolver function in the backends `resolvers.py` function. This is how the backend knows what to send to the frontend. A resolver function is ALWAYS named `resolver_something`. Note that Python uses camel case and GraphQL does not. This means that in communication the frontend is going to capitalize the letter(s) after the underscore(s) and remove the underscore. For example, the resolver, `resolve_get_winners_list`, will be referenced as `getWinnersList` in the frontend.
581+
582+
All resolvers must also be referenced in the `schema.py` file. Here they are again named using camel case and given return types, often the type is just the model from `models.py`.
583+
584+
If you are making the first queries of this backend folder you will also need to add the schema to `backend/config/schema.py`. This is to tell the command `python manage.py graphql_schema` to include the new schema file that it exists.
585+
586+
After adding the new resolvers you can run `docker compose exec backend python manage.py graphql_schema` in the `indok-web` directory to add the new schema. The query is now done from the backend perspective. NOTE: if you have forgotten to add the resolver to the schema or done so incorrectly the terminal will say it was successful. After this command, there should be a change in `schema.json`.
587+
588+
##### Queries in Frontend
589+
590+
In the frontend all query defining is done in the `graphql` folder. The generated folder should not be touched! (However it can be useful to look at in troubleshooting).
591+
592+
The `graphql` folder usually has 3 files, mutations, queries and fragments. Fragemnts are selections of fields on one of our API types, that can be reused across different queries/mutations to ensure consistent types. You can read more here: (https://www.apollographql.com/docs/react/data/fragments/). If a selection is used only once it can be written in the queries file directy.
593+
594+
After having written your query run the command `yarn generate` in the frontend directory to generate machine readable queries.
595+
596+
After the above is done the queries can be used and tested. In code they are used using the apollo function useQuery. Here is an example: `const { error, loading, data } = useQuery(GetWinnersListDocument);`. Notice the three terms before the query. Things can ALWAYS go wrong with queries and they requre data to be sent, therefore it is common to define what is done in rare cases.
597+
598+
- `data` is the normal data when the query has returned something expected from the backend.
599+
- `loading` is a waiting status. Queries are not instant and it can be useful to define some action while waiting.
600+
- `error` is what you recive if the query returned that something has gone wrong. This should almost always have some sort of notification to the user and or admins.
601+
602+
An important security notice: Permissions are handled in the backend. It is possible for anyone to try to call queries withut a frontend interface, it is therefore important that all queries that should have a permission check has them before it is pushed to production regardless of if there exists a frontend interface.
603+
604+
One last note about queries is that we limit size. If you return more than about 300 items the query will fail. Therefore it is important that sorting and things of that kind is done in the backend.
605+
606+
#### Making a mutation
607+
608+
A mutation is a request for data to be changed or added made to the backend by the frontend. The following is a guide on how to make mutations. Note that it is very similar to making a query.
609+
610+
##### Queries in Backend
611+
612+
All mutations have a respective mutation class in the backends `mutations.py` file. This is how the backend knows what to change when the mutation is called. A mutation class has no strict naming scheme but it is higly reccommended to end the name in "Mutation". Every mutation class has a function named mutate that does the changing, an Arguments class that takes in the data that can be passed from the frontend, and an ok that is returned if the mutation is successful.
613+
614+
All mutations must also be referenced in the `schema.py` file. Here they are named using camel case for the name of the mutation.
615+
616+
If you are making the first mutations of this backend folder you will also need to add the schema to `backend/config/schema.py`. This is to tell the command `python manage.py graphql_schema` to include the new schema file that it exists.
617+
618+
After adding the new resolvers you can run `docker compose exec backend python manage.py graphql_schema` in the `indok-web` directory to add the new schema. The mutation is now done from the backend perspective. NOTE: if you have forgotten to add the resolver to the schema or done so incorrectly the terminal will say it was successful. After this command, there should be a change in `schema.json`.
619+
620+
An important security notice: Permissions are handled in the backend. It is possible for anyone to try to call mutations withut a frontend interface, it is therefore important that all mutations that should have a permission check has it before it is pushed to production regardless of if there exists a frontend interface.
621+
622+
##### Mutations in Frontend
623+
624+
In the frontend all mutation defining is done in the `graphql` folder. The generated folder should not be touched! (However it can be useful to look at in troubleshooting).
625+
626+
The `graphql` folder usually has 3 files, mutations, queries and fragments. Obvously mutations are written in the mutations folder.
627+
628+
After having written your mutation run the command `yarn generate` in the frontend directory to generate machine readable mutation.
629+
630+
After the above is done the queries can be used and tested. In code they are used using the apollo function useUsemutation to define a function that can be run. Here is an example: `const [logTicTacToe] = useMutation(LogTicTacToeDocument);`. After this the `logTicTacToe` function can be used to run the mutation. It lookes like this: `logTicTacToe({ variables: { winner: "Draw" } })`; Note that this does not trigger a react rerender so if you need to change visuals you need to handle that as well.
631+
632+
An important security notice: Permissions are handled in the backend. It is possible for anyone to try to call queries withut a frontend interface, it is therefore important that all queries that should have a permission check has them before it is pushed to production regardless of if there exists a frontend interface. Most mutations (mabye all the ones we currenly have) need a permission check.
564633

565634
## Tech Stack
566635

backend/apps/ecommerce/resolvers.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,22 @@ def resolve_user_orders(self, info):
3838
def resolve_all_user_orders(self, info):
3939
return Order.objects.all()
4040

41+
@login_required
4142
@staff_member_required
42-
def resolve_all_shop_orders(self, info):
43-
return Order.objects.filter(product__shop_item=True)
43+
def resolve_paginated_shop_orders(self, info, limit, offset):
44+
# Apply pagination if limit and offset are provided
45+
orders = Order.objects.filter(product__shop_item=True).order_by(
46+
"delivered_product", "payment_status", "timestamp"
47+
)
48+
if offset is None:
49+
offset = 0
50+
orders = orders[offset:]
51+
52+
if limit is None or limit > 300:
53+
# Add hard cap of maximum 300 orders per query. A bigger query would crash the website
54+
limit = 300
55+
orders = orders[:limit]
56+
return orders
4457

4558
@staff_member_required
4659
def resolve_orders_by_status(self, info: "ResolveInfo", product_id, status):

backend/apps/ecommerce/schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class EcommerceQueries(graphene.ObjectType, EcommerceResolvers):
1919
order = graphene.Field(OrderType, order_id=graphene.ID(required=True))
2020
user_orders = graphene.List(NonNull(OrderType))
2121
all_user_orders = graphene.List(NonNull(OrderType))
22-
all_shop_orders = graphene.List(NonNull(OrderType))
22+
paginated_shop_orders = graphene.List(graphene.NonNull(OrderType), limit=graphene.Int(), offset=graphene.Int())
2323

2424
orders_by_status = graphene.Field(
2525
OrdersByStatusType, product_id=graphene.ID(required=True), status=graphene.String(required=True)

backend/apps/ecommerce/tests.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,85 @@ def test_delivered_product(self) -> None:
457457

458458
order = Order.objects.get(pk=order.id)
459459
self.assertTrue(order.delivered_product)
460+
461+
462+
class PaginatedShopOrdersResolverTests(ExtendedGraphQLTestCase):
463+
def setUp(self):
464+
# Create a staff user using the StaffUserFactory
465+
self.staff_user = StaffUserFactory(username="staffuser")
466+
467+
# Ensure the user satisfies the requirements for the new decorators
468+
self.staff_user.is_staff = True # Required for @staff_member_required
469+
self.staff_user.is_superuser = True # Required for @superuser_required if added
470+
self.staff_user.save()
471+
472+
# Create a product with `shop_item=True` to pass the resolver's filter condition
473+
self.product = ProductFactory(
474+
name="Test Product",
475+
price=decimal.Decimal("1000.00"),
476+
description="A test product description",
477+
max_buyable_quantity=2,
478+
total_quantity=5,
479+
shop_item=True, # Ensure this matches the resolver's filter condition
480+
)
481+
482+
# Create multiple orders associated with the product and user
483+
self.orders = [
484+
OrderFactory(
485+
product=self.product,
486+
user=self.staff_user,
487+
payment_status=Order.PaymentStatus.INITIATED,
488+
)
489+
for i in range(10)
490+
]
491+
492+
def test_paginated_shop_orders_with_fragment_and_product(self):
493+
query = """
494+
query paginatedShopOrders($limit: Int, $offset: Int) {
495+
paginatedShopOrders(limit: $limit, offset: $offset) {
496+
...Order
497+
}
498+
}
499+
500+
fragment Order on OrderType {
501+
id
502+
quantity
503+
totalPrice
504+
paymentStatus
505+
timestamp
506+
deliveredProduct
507+
product {
508+
...Product
509+
}
510+
}
511+
512+
fragment Product on ProductType {
513+
id
514+
name
515+
price
516+
description
517+
maxBuyableQuantity
518+
shopItem
519+
}
520+
"""
521+
522+
# Execute the query using the query method from ExtendedGraphQLTestCase
523+
response = self.query(query, variables={"limit": 5, "offset": 2}, user=self.staff_user) # Use staff user
524+
data = json.loads(response.content)
525+
526+
# Check if the response data matches expectations
527+
self.assertIn("data", data)
528+
self.assertIn("paginatedShopOrders", data["data"])
529+
self.assertEqual(len(data["data"]["paginatedShopOrders"]), 5)
530+
531+
# Verify the structure of the nested product field in the first order
532+
first_order = data["data"]["paginatedShopOrders"][0]
533+
self.assertIn("product", first_order)
534+
self.assertEqual(first_order["product"]["name"], "Test Product")
535+
self.assertEqual(first_order["product"]["price"], "1000.00") # Adjusted for consistency
536+
self.assertTrue(first_order["product"]["shopItem"])
537+
538+
# Additional checks for other fields
539+
self.assertIn("quantity", first_order)
540+
self.assertIn("paymentStatus", first_order)
541+
self.assertIn("timestamp", first_order)

backend/schema.json

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,11 +301,32 @@
301301
}
302302
},
303303
{
304-
"args": [],
304+
"args": [
305+
{
306+
"defaultValue": null,
307+
"description": null,
308+
"name": "limit",
309+
"type": {
310+
"kind": "SCALAR",
311+
"name": "Int",
312+
"ofType": null
313+
}
314+
},
315+
{
316+
"defaultValue": null,
317+
"description": null,
318+
"name": "offset",
319+
"type": {
320+
"kind": "SCALAR",
321+
"name": "Int",
322+
"ofType": null
323+
}
324+
}
325+
],
305326
"deprecationReason": null,
306327
"description": null,
307328
"isDeprecated": false,
308-
"name": "allShopOrders",
329+
"name": "paginatedShopOrders",
309330
"type": {
310331
"kind": "LIST",
311332
"name": null,

frontend/content/organizations/eiv.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ logo: /img/eivlogo.jpg
55
image: /img/eivbilde.jpg
66
board:
77
ceo:
8-
name: Johan Martin Emberland Johnsen
9-
title: Sommelier
10-
8+
name: Olav Utle Kolltveit
9+
title: Head of Red
10+
cto:
11+
name: Nora Vengstad Furøy
12+
title: Head of Bread
1113
tag: kultur
1214
---
1315

@@ -20,4 +22,4 @@ Fagseksjonen for EiV legger grunnlaget for å gradere friskhet, sødme, fylde og
2022
![Vin på Indøk!](/img/eivbilde.jpg)
2123

2224
Ved spørsmål ta kontakt med styrets sommelierer:
23-
Phillip Kolkmeier, Erlend Heir, Haakon Døssland og Ane Sandvik Rognskaug
25+
Olav Utle Kolltveit og Nora Vengstad Furøy

frontend/content/organizations/rubberdok.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ logo: /img/rubberdok_logo_black.svg
55
image: /img/rubberdoklogo1.png
66
alt: Logoen til rubberdøk
77
board:
8-
cto:
9-
name: Magnus Hafstad
10-
title: Prosjektleder
11-
mail: magnus.hafstad@rubberdok.no
128
ceo:
139
name: Hannah Köberle
1410
title: Prosjektleder
15-
mail: hannah.koeberle@rubberdok.no
11+
mail: leder@rubberdok.no
12+
cto:
13+
name: Oda Fu Zhe Runde
14+
title: Prosjektleder
15+
mail: leder@rubberdok.no
1616
tag: annet
1717
---
1818

frontend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
"@graphql-codegen/typescript": "4.0.6",
5959
"@graphql-codegen/typescript-operations": "4.2.3",
6060
"@graphql-typed-document-node/core": "^3.2.0",
61-
"@next/bundle-analyzer": "^13.4.19",
61+
"@next/bundle-analyzer": "^14.2.7",
6262
"@parcel/watcher": "^2.4.1",
63-
"@repeaterjs/repeater": "^3.0.4",
63+
"@repeaterjs/repeater": "^3.0.6",
6464
"@types/lodash": "^4.14.197",
6565
"@types/node": "18",
6666
"@types/react": "18.2.46",
-1.2 MB
Loading

0 commit comments

Comments
 (0)