diff --git a/README.md b/README.md
index d55208e..89fc073 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
# cal_counter
Calorie Counter Webapp
+
+Bello -Iman
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..77c5c4d
--- /dev/null
+++ b/app.py
@@ -0,0 +1,65 @@
+from flask import render_template, redirect, url_for
+from flask_login import (
+ login_required,
+ logout_user,
+ login_user,
+ current_user,
+)
+from forms import LoginForm
+from models import User
+from application import db, app, login_manager
+
+# Config
+app.config["SECRET_KEY"] = "Iman"
+
+login_manager.login_view = "login" # type: ignore
+
+
+@login_manager.user_loader # type: ignore
+def load_user(user_id: int):
+ return db.session.get(User, user_id)
+
+
+# Routes
+
+
+@app.route("/")
+def index():
+ return render_template("index.html")
+
+
+@app.route("/login", methods=["GET", "POST"])
+def login():
+ if current_user.is_authenticated:
+ return redirect(url_for("dashboard"))
+
+ form = LoginForm()
+ if form.validate_on_submit():
+ user = User.query.filter_by(username=form.username.data).first()
+ if user and user.check_password(password=form.password.data):
+ # User found and password correct
+ login_user(user)
+ return redirect(url_for("dashboard"))
+ else:
+ pass
+ # invalid user
+ return render_template("login.html", form=form)
+
+
+@app.route("/dashboard")
+@login_required
+def dashboard():
+ return render_template("dashboard.html", name=current_user.username)
+
+
+@app.route("/logout")
+@login_required
+def logout():
+ logout_user()
+ return redirect(url_for("index"))
+
+
+# Run
+
+if __name__ == "__main__":
+ app.run(debug=True)
diff --git a/application/__init__.py b/application/__init__.py
new file mode 100644
index 0000000..5c935b1
--- /dev/null
+++ b/application/__init__.py
@@ -0,0 +1,18 @@
+from flask import Flask
+from flask_login import LoginManager # type: ignore
+from flask_sqlalchemy import SQLAlchemy
+from flask_migrate import Migrate
+from application.admin.routes import admin_bp
+
+
+app = Flask(__name__) # Init Flask app
+app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db"
+
+
+db = SQLAlchemy(app=app) # Init SQLAlchemy
+migrate = Migrate(app=app, db=db) # Init Migration
+
+login_manager = LoginManager(app=app) # Init login manager
+
+# Register blueprints
+app.register_blueprint(admin_bp)
diff --git a/application/admin/routes.py b/application/admin/routes.py
new file mode 100644
index 0000000..ea4fb56
--- /dev/null
+++ b/application/admin/routes.py
@@ -0,0 +1,13 @@
+from flask import Blueprint, render_template
+
+admin_bp = Blueprint(
+ "admin",
+ __name__,
+ url_prefix="/admin",
+ template_folder="templates",
+)
+
+
+@admin_bp.route("/food_items", methods=["GET"])
+def food_items():
+ return render_template("food_items.html")
diff --git a/application/admin/templates/food_items.html b/application/admin/templates/food_items.html
new file mode 100644
index 0000000..9f7f311
--- /dev/null
+++ b/application/admin/templates/food_items.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+
+{% block content %}
+Hallo
+{% endblock%}
\ No newline at end of file
diff --git a/application/templates/base.html b/application/templates/base.html
new file mode 100644
index 0000000..9a786f7
--- /dev/null
+++ b/application/templates/base.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ {% block title %}My Flask App{% endblock %}
+
+
+
+
+
+
+
+
+
+
+ {% with messages = get_flashed_messages() %}
+ {% if messages %}
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
+ {% endwith %}
+
+ {% block content %}{% endblock %}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/application/templates/dashboard.html b/application/templates/dashboard.html
new file mode 100644
index 0000000..9f7f311
--- /dev/null
+++ b/application/templates/dashboard.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+
+{% block content %}
+Hallo
+{% endblock%}
\ No newline at end of file
diff --git a/application/templates/index.html b/application/templates/index.html
new file mode 100644
index 0000000..74682ea
--- /dev/null
+++ b/application/templates/index.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+
+{% block content %}
+Index page idk
+{% endblock%}
\ No newline at end of file
diff --git a/application/templates/login.html b/application/templates/login.html
new file mode 100644
index 0000000..20fff43
--- /dev/null
+++ b/application/templates/login.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{% endblock%}
\ No newline at end of file
diff --git a/forms.py b/forms.py
new file mode 100644
index 0000000..31abd81
--- /dev/null
+++ b/forms.py
@@ -0,0 +1,9 @@
+from flask_wtf import FlaskForm
+from wtforms import StringField, PasswordField, SubmitField
+from wtforms.validators import DataRequired
+
+
+class LoginForm(FlaskForm):
+ username = StringField("Username", validators=[DataRequired()])
+ password = PasswordField("Password", validators=[DataRequired()])
+ submit = SubmitField("Login")
diff --git a/migrations/README b/migrations/README
new file mode 100644
index 0000000..0e04844
--- /dev/null
+++ b/migrations/README
@@ -0,0 +1 @@
+Single-database configuration for Flask.
diff --git a/migrations/alembic.ini b/migrations/alembic.ini
new file mode 100644
index 0000000..ec9d45c
--- /dev/null
+++ b/migrations/alembic.ini
@@ -0,0 +1,50 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic,flask_migrate
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[logger_flask_migrate]
+level = INFO
+handlers =
+qualname = flask_migrate
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/migrations/env.py b/migrations/env.py
new file mode 100644
index 0000000..4c97092
--- /dev/null
+++ b/migrations/env.py
@@ -0,0 +1,113 @@
+import logging
+from logging.config import fileConfig
+
+from flask import current_app
+
+from alembic import context
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+
+def get_engine():
+ try:
+ # this works with Flask-SQLAlchemy<3 and Alchemical
+ return current_app.extensions['migrate'].db.get_engine()
+ except (TypeError, AttributeError):
+ # this works with Flask-SQLAlchemy>=3
+ return current_app.extensions['migrate'].db.engine
+
+
+def get_engine_url():
+ try:
+ return get_engine().url.render_as_string(hide_password=False).replace(
+ '%', '%%')
+ except AttributeError:
+ return str(get_engine().url).replace('%', '%%')
+
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+config.set_main_option('sqlalchemy.url', get_engine_url())
+target_db = current_app.extensions['migrate'].db
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def get_metadata():
+ if hasattr(target_db, 'metadatas'):
+ return target_db.metadatas[None]
+ return target_db.metadata
+
+
+def run_migrations_offline():
+ """Run migrations in 'offline' mode.
+
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+
+ Calls to context.execute() here emit the given string to the
+ script output.
+
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(
+ url=url, target_metadata=get_metadata(), literal_binds=True
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def run_migrations_online():
+ """Run migrations in 'online' mode.
+
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+
+ """
+
+ # this callback is used to prevent an auto-migration from being generated
+ # when there are no changes to the schema
+ # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
+ def process_revision_directives(context, revision, directives):
+ if getattr(config.cmd_opts, 'autogenerate', False):
+ script = directives[0]
+ if script.upgrade_ops.is_empty():
+ directives[:] = []
+ logger.info('No changes in schema detected.')
+
+ conf_args = current_app.extensions['migrate'].configure_args
+ if conf_args.get("process_revision_directives") is None:
+ conf_args["process_revision_directives"] = process_revision_directives
+
+ connectable = get_engine()
+
+ with connectable.connect() as connection:
+ context.configure(
+ connection=connection,
+ target_metadata=get_metadata(),
+ **conf_args
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/migrations/script.py.mako b/migrations/script.py.mako
new file mode 100644
index 0000000..2c01563
--- /dev/null
+++ b/migrations/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+ ${downgrades if downgrades else "pass"}
diff --git a/migrations/versions/319d293f3017_.py b/migrations/versions/319d293f3017_.py
new file mode 100644
index 0000000..cde2f21
--- /dev/null
+++ b/migrations/versions/319d293f3017_.py
@@ -0,0 +1,56 @@
+"""empty message
+
+Revision ID: 319d293f3017
+Revises: aab8050e9c73
+Create Date: 2025-06-26 10:35:18.540813
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '319d293f3017'
+down_revision = 'aab8050e9c73'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('unit',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('symbol', sa.String(length=10), nullable=False),
+ sa.Column('name', sa.String(length=50), nullable=False),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('name'),
+ sa.UniqueConstraint('symbol')
+ )
+ op.create_table('food_item',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('name', sa.String(length=150), nullable=False),
+ sa.Column('amount', sa.Integer(), nullable=False),
+ sa.Column('unit_id', sa.Integer(), nullable=False),
+ sa.Column('energy', sa.Float(), nullable=True),
+ sa.Column('protein', sa.Float(), nullable=True),
+ sa.Column('carbs', sa.Float(), nullable=True),
+ sa.Column('sugar', sa.Float(), nullable=True),
+ sa.Column('fats', sa.Float(), nullable=True),
+ sa.Column('saturated_fats', sa.Float(), nullable=True),
+ sa.ForeignKeyConstraint(['unit_id'], ['unit.id'], ),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('name')
+ )
+ op.drop_table('units')
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('units',
+ sa.Column('id', sa.INTEGER(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.drop_table('food_item')
+ op.drop_table('unit')
+ # ### end Alembic commands ###
diff --git a/migrations/versions/aab8050e9c73_.py b/migrations/versions/aab8050e9c73_.py
new file mode 100644
index 0000000..f636b43
--- /dev/null
+++ b/migrations/versions/aab8050e9c73_.py
@@ -0,0 +1,39 @@
+"""empty message
+
+Revision ID: aab8050e9c73
+Revises:
+Create Date: 2025-06-26 10:24:25.760737
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'aab8050e9c73'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('units',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('user',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('username', sa.String(length=150), nullable=False),
+ sa.Column('password', sa.String(length=150), nullable=False),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('username')
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('user')
+ op.drop_table('units')
+ # ### end Alembic commands ###
diff --git a/models.py b/models.py
new file mode 100644
index 0000000..47fba6b
--- /dev/null
+++ b/models.py
@@ -0,0 +1,45 @@
+from flask_login import UserMixin # type: ignore
+from werkzeug.security import generate_password_hash, check_password_hash
+from application import db
+
+
+class User(UserMixin, db.Model):
+ __tablename__ = "user"
+ id = db.Column(db.Integer, primary_key=True)
+ username = db.Column(db.String(150), unique=True, nullable=False)
+ password = db.Column(db.String, nullable=False)
+
+ def __init__(self, username: str, password: str):
+ super().__init__()
+ self.username = username
+ self.password = generate_password_hash(password=password)
+
+ def check_password(self, password: str) -> bool:
+ return check_password_hash(pwhash=self.password, password=password)
+
+ def change_password(self, password: str) -> None:
+ self.password = generate_password_hash(password=password)
+
+
+class Units(db.Model):
+ __tablename__ = "unit"
+ id = db.Column(db.Integer, primary_key=True)
+ symbol = db.Column(db.String(10), unique=True, nullable=False)
+ name = db.Column(db.String(50), unique=True, nullable=False)
+
+
+class FoodItems(db.Model):
+ __tablename__ = "food_item"
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(150), unique=True, nullable=False)
+ amount = db.Column(db.Integer, nullable=False)
+
+ unit_id = db.Column(db.Integer, db.ForeignKey("unit.id"), nullable=False)
+ unit = db.relationship("Units")
+
+ energy = db.Column(db.Float)
+ protein = db.Column(db.Float)
+ carbs = db.Column(db.Float)
+ sugar = db.Column(db.Float)
+ fats = db.Column(db.Float)
+ saturated_fats = db.Column(db.Float)
diff --git a/pyrightconfig.json b/pyrightconfig.json
new file mode 100644
index 0000000..72063c2
--- /dev/null
+++ b/pyrightconfig.json
@@ -0,0 +1,6 @@
+{
+ "exclude": [
+ "migrations",
+ ".venv",
+ ]
+}
\ No newline at end of file
diff --git a/seed.py b/seed.py
new file mode 100644
index 0000000..963b42e
--- /dev/null
+++ b/seed.py
@@ -0,0 +1,7 @@
+from application import db, app
+from models import User
+
+with app.app_context():
+ User.query.delete()
+ db.session.add(User("admin", "admin"))
+ db.session.commit()